hiệu suất của số nguyên không dấu so với số nguyên có dấu


79

Có bất kỳ sự tăng / giảm hiệu suất nào bằng cách sử dụng số nguyên không dấu thay cho số nguyên có dấu không?

Nếu vậy, điều này có diễn ra trong ngắn hạn và dài hạn không?


9
Không đủ mà bạn cần phải quan tâm đến nó.
JeremyP

17
@JeremyP, tôi có thể đề nghị bạn nên nói sự thật chỉ cho phần lớn các nhà phát triển và các ứng dụng ....
Brett

1
@Brett: Sự khác biệt giữa số học có dấu và không dấu trên hầu hết các CPU là 0. Sự khác biệt đối với các kích thước khác nhau là không đáng kể trừ khi bạn đang làm nhiều phép tính.
JeremyP

Câu trả lời:


108

Chia theo lũy thừa của 2 nhanh hơn với unsigned int, vì nó có thể được tối ưu hóa thành một lệnh dịch chuyển duy nhất. Với signed int, nó thường yêu cầu nhiều lệnh máy hơn, vì phép chia làm tròn về 0 , nhưng chuyển sang phải làm tròn xuống . Thí dụ:

int foo(int x, unsigned y)
{
    x /= 8;
    y /= 8;
    return x + y;
}

Đây là phần liên quan x(bộ phận đã ký):

movl 8(%ebp), %eax
leal 7(%eax), %edx
testl %eax, %eax
cmovs %edx, %eax
sarl $3, %eax

Và đây là phần liên quan y(bộ phận không dấu):

movl 12(%ebp), %edx
shrl $3, %edx

11
Điều này sẽ chỉ hoạt động trong trường hợp ước số là một hằng số đã biết theo thời gian tuân thủ là lũy thừa của hai, phải không?
sharptooth,

1
@sharptooth, để phân chia, có. Có thể có các thủ thuật thao tác bit khác chỉ có giá trị cho không dấu. Hoặc đã ký. Tôi không nghĩ rằng tác động tích cực chỉ theo một hướng.
AProgrammer,

Tại sao mẹo không thể thực hiện được đối với các ước không hằng số? Toán hạng đầu tiên của x86 shrlphải là một chữ?
Manu343726

@ Manu343726 Điều gì sẽ xảy ra nếu số chia không phải là lũy thừa của 2? (Và ngay cả khi nó đã được, trước tiên bạn phải tính toán logarit nhị phân của số trước khi chuyển.)
fredoverflow

1
Trên quy mô này, nhiều lệnh hơn không phải lúc nào cũng có nghĩa là thời gian chạy chậm hơn đối với các kiến ​​trúc CPU có đường ống hiện đại. Tức là tôi vẫn sẽ thực hiện một phép đo trước khi đưa ra kết luận sâu rộng.
ulidtko

49

Trong C ++ (và C), tràn số nguyên có dấu là không xác định, trong khi tràn số nguyên không dấu được xác định để bao quanh. Lưu ý rằng ví dụ: trong gcc, bạn có thể sử dụng cờ -fwrapv để xác định phần tràn có dấu (để quấn quanh).

Tràn số nguyên có dấu không xác định cho phép trình biên dịch giả định rằng lỗi tràn không xảy ra, điều này có thể giới thiệu cơ hội tối ưu hóa. Xem ví dụ: bài đăng trên blog này để thảo luận.


20

unsigneddẫn đến hiệu suất tương tự hoặc tốt hơn signed. Vài ví dụ:

  • Chia cho một hằng số là lũy thừa của 2 (xem thêm câu trả lời từ FredOverflow )
  • Chia cho một số không đổi (ví dụ: trình biên dịch của tôi thực hiện phép chia cho 13 bằng cách sử dụng 2 asm hướng dẫn cho chưa dấu và 6 hướng dẫn có dấu)
  • Kiểm tra xem một số có phải là số chẵn hay không (tôi không biết tại sao trình biên dịch MS Visual Studio của tôi triển khai nó với 4 hướng dẫn cho các signedsố; gcc thực hiện điều đó với 1 lệnh, giống như trong unsignedtrường hợp)

shortthường dẫn đến hiệu suất tương tự hoặc kém hơn int(giả định sizeof(short) < sizeof(int)). Sự suy giảm hiệu suất xảy ra khi bạn gán kết quả của một phép toán số học (thường là int, không bao giờ short) cho một biến kiểu short, được lưu trong thanh ghi của bộ xử lý (cũng là kiểu int). Tất cả các chuyển đổi từ shortđể intmất thời gian và gây phiền nhiễu.

Lưu ý: một số DSP có hướng dẫn nhân nhanh cho signed shortloại; trong trường hợp cụ thể này shortlà nhanh hơn int.

Đối với sự khác biệt giữa intlong, tôi chỉ có thể đoán (tôi không quen thuộc với kiến ​​trúc 64-bit). Tất nhiên, nếu intlongcó cùng kích thước (trên nền tảng 32 bit), hiệu suất của chúng cũng giống nhau.


Một bổ sung rất quan trọng, được một số người chỉ ra:

Điều thực sự quan trọng đối với hầu hết các ứng dụng là dung lượng bộ nhớ và băng thông được sử dụng. Bạn nên sử dụng các số nguyên cần thiết nhỏ nhất ( shortthậm chí có thể signed/unsigned char) cho các mảng lớn.

Điều này sẽ cho hiệu suất tốt hơn, nhưng mức tăng là phi tuyến tính (nghĩa là không theo hệ số 2 hoặc 4) và hơi khó đoán - nó phụ thuộc vào kích thước bộ nhớ cache và mối quan hệ giữa các phép tính và truyền bộ nhớ trong ứng dụng của bạn.


8
Tôi sẽ cẩn thận với tuyên bố về hiệu suất của short so với int. Mặc dù số học "có thể" nhanh hơn bằng cách sử dụng int, người ta nên nhớ rằng số học nguyên hiếm khi là một nút cổ chai (ít nhất là trên các cpu máy tính để bàn hiện đại), băng thông bộ nhớ thường là vậy, vì vậy đối với các tập dữ liệu lớn, ngắn có thể thực sự mang lại hiệu suất tốt hơn đáng kể sau đó int. Hơn nữa, đối với mã tự động hóa bằng cách sử dụng kiểu dữ liệu nhỏ hơn thường có nghĩa là nhiều phần tử dữ liệu có thể được đánh giá cùng một lúc, do đó, ngay cả hiệu suất số học cũng có thể tăng lên (mặc dù không chắc với trạng thái hiện tại của trình tự động hóa).
Grizzly

1
@Grizzly Tôi đồng ý (ứng dụng của tôi thực sự là tính toán nặng, vì vậy kinh nghiệm của tôi với shortlà khác nhau hơn của bạn / bất cứ ai khác)
anatolyg

2
@martinkunev Hoàn toàn có thể! Đây có thể là lý do duy nhất để sử dụng shortngày nay (với RAM không có bộ nhớ đệm hiệu quả là vô hạn) và là một lý do rất tốt.
anatolyg

1
@anatolyg RAM có thể là vô hạn, nhưng đừng quên rằng các chương trình 32 bit vẫn nhiều hơn các chương trình 64 bit một biên độ lớn, có nghĩa là bất kể dung lượng RAM có sẵn, bạn vẫn thường bị giới hạn ở 2GB địa chỉ có thể sử dụng -không gian.
bcrist

1
@JoshParnell Tôi đoán ý bạn shortlà nhanh hơn intkhi bộ nhớ bị giới hạn . Theo kinh nghiệm của tôi là chúng có cùng hiệu suất trên x86 và shortchậm hơn trên ARM.
anatolyg

17

Điều này sẽ phụ thuộc vào việc thực hiện chính xác. Tuy nhiên, trong hầu hết các trường hợp sẽ không có sự khác biệt. Nếu bạn thực sự quan tâm, bạn phải thử tất cả các biến thể mà bạn xem xét và đo lường hiệu suất.


21
+1vì "muốn biết thì phải đo". Thật khó chịu khi điều này cần được trả lời gần như hàng tuần.
sbi

9

Điều này phụ thuộc khá nhiều vào bộ vi xử lý cụ thể.

Trên hầu hết các bộ xử lý, có các hướng dẫn cho cả số học có dấu và không dấu, do đó, sự khác biệt giữa việc sử dụng số nguyên có dấu và không dấu phụ thuộc vào cách mà trình biên dịch sử dụng.

Nếu bất kỳ cái nào trong hai cái nhanh hơn, thì đó là bộ xử lý hoàn toàn cụ thể và rất có thể sự khác biệt là rất nhỏ, nếu nó tồn tại.


7

Sự khác biệt về hiệu suất giữa số nguyên có dấu và không có dấu thực sự chung chung hơn câu trả lời chấp nhận đề xuất. Phép chia một số nguyên không dấu cho bất kỳ hằng số nào có thể được thực hiện nhanh hơn phép chia một số nguyên có dấu cho một hằng số, bất kể hằng số đó có phải là lũy thừa của hai hay không. Xem http://ridiculousfish.com/blog/posts/labor-of-division-ep Chap-iii.html

Ở cuối bài đăng của mình, anh ấy bao gồm phần sau:

Một câu hỏi tự nhiên là liệu việc tối ưu hóa tương tự có thể cải thiện sự phân chia đã ký; Rất tiếc, có vẻ như nó không, vì hai lý do:

Sự gia tăng của cổ tức phải trở nên tăng về mức độ, tức là tăng nếu n> 0, giảm nếu n <0. Điều này dẫn đến một khoản chi phí bổ sung.

Hình phạt cho phép chia không hợp tác chỉ bằng khoảng một nửa trong phép chia có chữ ký, để lại một cơ hội cải tiến nhỏ hơn.

Do đó, có vẻ như thuật toán làm tròn xuống có thể được thực hiện để hoạt động trong phép chia có dấu, nhưng sẽ kém hiệu quả hơn thuật toán làm tròn tiêu chuẩn.


4

Không chỉ phép chia cho lũy thừa của 2 nhanh hơn với kiểu không dấu, phép chia cho bất kỳ giá trị nào khác cũng nhanh hơn với kiểu không dấu. Nếu bạn nhìn vào bảng Hướng dẫn của Agner Fog, bạn sẽ thấy rằng các bộ phận không có dấu có hiệu suất tương tự hoặc tốt hơn các phiên bản đã ký

Ví dụ với AMD K7

Chỉ dẫn Toán hạng Hoạt động Độ trễ Thông lượng đối ứng
DIV r8 / m8 32 24 23
DIV r16 / m16 47 24 23
DIV r32 / m32 79 40 40
IDIV r8 41 17 17
IDIV r16 56 25 25
IDIV r32 88 41 41
IDIV m8 42 17 17
IDIV m16 57 25 25
IDIV m32 89 41 41

Điều tương tự cũng áp dụng cho Intel Pentium

Chỉ dẫn Toán hạng Chu kỳ đồng hồ
DIV r8 / m8 17
DIV r16 / m16 25
DIV r32 / m32 41
IDIV r8 / m8 22
IDIV r16 / m16 30
IDIV r32 / m32 46

Tất nhiên đó là những thứ khá cổ. Các kiến ​​trúc mới hơn với nhiều bóng bán dẫn hơn có thể thu hẹp khoảng cách nhưng những điều cơ bản vẫn áp dụng: nói chung bạn cần nhiều vi hoạt động hơn, logic hơn, độ trễ nhiều hơn để thực hiện phép phân chia đã ký


3

Tóm lại, đừng bận tâm trước thực tế. Nhưng làm phiền sau.

Nếu bạn muốn có hiệu suất, bạn phải sử dụng các tính năng tối ưu hóa hiệu suất của một trình biên dịch có thể hoạt động trái với lẽ thường. Một điều cần nhớ là các trình biên dịch khác nhau có thể biên dịch mã khác nhau và bản thân chúng có các loại tối ưu hóa khác nhau. Nếu chúng ta đang nói về một g++trình biên dịch và nói về việc tăng tối đa mức tối ưu hóa của nó bằng cách sử dụng -Ofast, hoặc ít nhất là một -O3cờ, thì theo kinh nghiệm của tôi, nó có thể biên dịch longloại thành mã với hiệu suất tốt hơn bất kỳ unsignedloại nào , hoặc thậm chí chỉ int.

Đây là từ kinh nghiệm của riêng tôi và tôi khuyên bạn trước tiên nên viết chương trình đầy đủ của mình và chỉ quan tâm đến những thứ như vậy sau đó, khi bạn có mã thực tế trong tay và bạn có thể biên dịch nó với các tính năng tối ưu hóa để thử và chọn loại thực sự hoạt động tốt. Đây cũng là một gợi ý chung rất tốt về việc tối ưu hóa mã cho hiệu suất, trước tiên hãy viết nhanh, thử biên dịch với tối ưu hóa, tinh chỉnh mọi thứ để xem những gì hoạt động tốt nhất. Và bạn cũng nên thử sử dụng các trình biên dịch khác nhau để biên dịch chương trình của mình và chọn trình biên dịch xuất ra mã máy hiệu quả nhất.

Một chương trình tính toán đại số tuyến tính đa luồng được tối ưu hóa có thể dễ dàng có chênh lệch hiệu suất> 10 lần được tối ưu hóa tinh vi so với không được tối ưu hóa. Vì vậy, điều này có vấn đề.

Đầu ra của trình tối ưu hóa mâu thuẫn với logic trong nhiều trường hợp. Ví dụ, tôi đã gặp một trường hợp khi sự khác biệt giữa a[x]+=ba[x]=bthời gian thực thi chương trình thay đổi gần gấp đôi. Và không, a[x]=bkhông phải là nhanh hơn.

Đây là ví dụ NVidia nói rằng để lập trình GPU của họ:

Lưu ý: Như đã là phương pháp hay nhất được khuyến nghị, số học có dấu nên được ưu tiên hơn số học không dấu nếu có thể để có thông lượng tốt nhất trên SMM. Tiêu chuẩn ngôn ngữ C đặt ra nhiều hạn chế hơn đối với hành vi tràn đối với phép toán không dấu, hạn chế các cơ hội tối ưu hóa trình biên dịch.


1

IIRC, trên x86 có dấu / không dấu sẽ không tạo ra bất kỳ sự khác biệt nào. Mặt khác, ngắn / dài là một câu chuyện khác, vì lượng dữ liệu phải di chuyển đến / từ RAM sẽ lớn hơn trong thời gian dài (các lý do khác có thể bao gồm các hoạt động truyền như kéo dài từ ngắn sang dài).


1
Cũng nên nhớ rằng một số trình biên dịch nhất định có thể có các tối ưu hóa không áp dụng cho tất cả các kiểu số nguyên. Ví dụ: ít nhất các trình biên dịch cũ của Intel không thể áp dụng tự động hóa nếu bộ đếm vòng lặp vòng lặp là bất kỳ thứ gì khác ngoài một int đã ký.
CAFxX

nó không quan trọng ở cấp hướng dẫn nhưng từ ++ mức độ C nó không thành vấn đề
phuclv

@ LưuVĩnhPhúc bạn đang nói về việc tràn ký là UB? nếu vậy, trường hợp duy nhất mà tôi biết về vấn đề đó là trường hợp khó tối ưu hóa trình biên dịch hơn để suy luận về các bộ xen không dấu được sử dụng làm bộ đếm vòng lặp / biến cảm ứng (và điều này đã được đề cập trong nhận xét của tôi ngay phía trên của bạn)
CAFxX

Không, có nhiều trường hợp khác mà dấu hiệu quan trọng. Bạn đã đọc các câu trả lời khác?
phuclv

Tôi đã làm. Bạn đã? Hầu hết trong số họ nói rằng không có sự khác biệt lớn, trừ khi các phép chia biên dịch-thời gian-hằng số và cho các biến cảm ứng vòng lặp (mà tôi đã đề cập trong nhận xét của mình). Ngay cả trong máy của bạn bạn kinda chỉ ra rằng trong bộ vi xử lý mới hơn sự khác biệt là không lớn lắm (kiểm tra ví dụ như các bảng Sandy Bridge)
CAFxX

1

Các số nguyên có dấu và không dấu sẽ luôn hoạt động như các lệnh đồng hồ đơn và có cùng hiệu suất đọc-ghi nhưng theo Tiến sĩ Andrei Alexandrescu, không dấu được ưu tiên hơn là có dấu. Lý do cho điều này là bạn có thể lắp gấp đôi số lượng số trong cùng một số bit vì bạn không lãng phí bit dấu và bạn sẽ sử dụng ít hướng dẫn hơn để kiểm tra các số âm làm tăng hiệu suất từ ​​ROM giảm. Theo kinh nghiệm của tôi với Kabuki VM , có Tập lệnh hiệu suất cực caoThực hiện, hiếm khi bạn thực sự yêu cầu một số có dấu khi làm việc với bộ nhớ. Tôi đã dành nhiều năm để thực hiện số học con trỏ với các số có dấu và không dấu và tôi không thấy lợi ích gì cho việc có dấu khi không cần bit dấu.

Trường hợp có dấu có thể được ưu tiên hơn khi sử dụng dịch chuyển bit để thực hiện phép nhân và chia lũy thừa của 2 vì bạn có thể thực hiện phép chia lũy thừa âm của 2 phép chia với số nguyên bù của 2 có dấu. Vui lòng xem thêm một số video YouTube của Andrei để biết thêm các kỹ thuật tối ưu hóa. Bạn cũng có thể tìm thấy một số thông tin hữu ích trong bài viết của tôi về thuật toán chuyển đổi Số nguyên thành chuỗi nhanh nhất thế giới .


0

Theo truyền thống intlà định dạng số nguyên gốc của nền tảng phần cứng đích. Bất kỳ kiểu số nguyên nào khác đều có thể bị phạt hiệu suất.

BIÊN TẬP:

Mọi thứ hơi khác trên các hệ thống hiện đại:

  • intthực tế có thể là 32-bit trên hệ thống 64-bit vì lý do tương thích. Tôi tin rằng điều này xảy ra trên hệ thống Windows.

  • Các trình biên dịch hiện đại có thể sử dụng ngầm intkhi thực hiện các phép tính cho các kiểu ngắn hơn trong một số trường hợp.


vâng, theo truyền thống ;-) trên các hệ thống 64 bit hiện tại, intvẫn rộng 32 bit, nhưng loại 64 bit ( longhoặc long long, tùy thuộc vào hệ điều hành) ít nhất cũng phải nhanh.
Philipp

1
intluôn rộng 32 bit trên tất cả các hệ thống mà tôi biết (Windows, Linux, Mac OS X, bất kể bộ xử lý có phải là 64 bit hay không). Đó là longloại đó là khác nhau: 32 bit trên Windows, nhưng một từ trên Linux và OS X.
Philipp

@Philipp nhưng intkhông nhất thiết phải luôn rộng 32 bit.
glass0114,

0

Số nguyên không dấu có lợi ở chỗ bạn lưu trữ và coi cả hai như dòng bit, ý tôi là chỉ là một dữ liệu, không có dấu, vì vậy phép nhân, phép tính trở nên dễ dàng hơn (nhanh hơn) với các phép toán dịch chuyển bit

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.