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?
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?
Câu trả lời:
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
shrl
phải là một chữ?
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.
unsigned
dẫn đến hiệu suất tương tự hoặc tốt hơn signed
. Vài ví dụ:
signed
số; gcc thực hiện điều đó với 1 lệnh, giống như trong unsigned
trường hợp)short
thườ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
để int
mấ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 short
loại; trong trường hợp cụ thể này short
là nhanh hơn int
.
Đối với sự khác biệt giữa int
và long
, 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 int
và long
có 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 ( short
thậ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.
short
là khác nhau hơn của bạn / bất cứ ai khác)
short
ngà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.
short
là nhanh hơn int
khi 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à short
chậm hơn trên ARM.
Đ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.
+1
vì "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.
Đ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.
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.
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ý
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 -O3
cờ, thì theo kinh nghiệm của tôi, nó có thể biên dịch long
loại thành mã với hiệu suất tốt hơn bất kỳ unsigned
loạ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]+=b
và a[x]=b
thời gian thực thi chương trình thay đổi gần gấp đôi. Và không, a[x]=b
khô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.
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).
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 .
Theo truyền thống int
là đị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:
int
thự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 int
khi 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.
int
vẫn rộng 32 bit, nhưng loại 64 bit ( long
hoặc long long
, tùy thuộc vào hệ điều hành) ít nhất cũng phải nhanh.
int
luô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à long
loại đó là khác nhau: 32 bit trên Windows, nhưng một từ trên Linux và OS X.
int
không nhất thiết phải luôn rộng 32 bit.
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