Sự khác biệt hiệu suất giữa các số nguyên không dấu và đã ký là gì? [đóng cửa]


42

Tôi nhận thức được hiệu suất hit khi trộn ints đã ký với float.

Có bất kỳ tồi tệ hơn để trộn ints không dấu với phao?

Có bất kỳ hit khi trộn đã ký / không dấu mà không nổi?

Các kích thước khác nhau (u32, u16, u8, i32, i16, i8) có ảnh hưởng gì đến hiệu suất không? Trên nền tảng nào?


2
Tôi đã xóa văn bản / thẻ dành riêng cho PS3, bởi vì đây là một câu hỏi hay về bất kỳ kiến ​​trúc nào và câu trả lời đúng cho tất cả các kiến ​​trúc phân tách các thanh ghi số nguyên và dấu phẩy động, thực tế là tất cả chúng.

Câu trả lời:


36

Hình phạt lớn từ việc trộn ints (dưới bất kỳ hình thức nào) và float là bởi vì chúng nằm trong các bộ đăng ký khác nhau. Để đi từ một thanh ghi được đặt sang thanh ghi khác, bạn phải ghi giá trị vào bộ nhớ và đọc lại, điều này phát sinh một gian hàng tải-hit-store .

Đi giữa các kích cỡ khác nhau hoặc số lượng đã ký của ints giữ mọi thứ trong cùng một bộ đăng ký, do đó bạn tránh được hình phạt lớn. Có thể có các hình phạt nhỏ hơn do gia hạn đăng nhập, v.v. nhưng những hình phạt này nhỏ hơn nhiều so với tải-hit-store.


Bài viết mà bạn đã liên kết nói rằng Bộ xử lý di động PS3 là một ngoại lệ đối với điều này bởi vì rõ ràng mọi thứ được lưu trữ trong cùng một bộ đăng ký (có thể tìm thấy ở giữa bài viết hoặc tìm kiếm "Ô").
bummzack

4
@bummzack: Điều đó chỉ áp dụng cho các SPE, không phải PPE; các SPE có môi trường điểm nổi rất đặc biệt, đặc biệt, và dàn diễn viên vẫn còn tương đối đắt tiền. Ngoài ra, chi phí vẫn giống nhau cho các số nguyên đã ký so với không dấu.

Đó là một bài viết hay và điều quan trọng là phải biết về LHS (và tôi đang bỏ phiếu cho điều đó) nhưng câu hỏi của tôi là về những hình phạt liên quan đến dấu hiệu đó. Tôi biết đây là những số nhỏ và có thể không đáng kể, nhưng tôi vẫn muốn xem một số con số thực hoặc tài liệu tham khảo về chúng.
Luis

1
@Luis - Tôi đã cố gắng tìm một số tài liệu công khai về điều này nhưng không thể tìm thấy nó vào lúc này. Nếu bạn có quyền truy cập vào tài liệu Xbox360, có một bản trắng tốt của Bruce Dawson bao gồm một số thứ này (và nói chung nó rất tốt).
celion

@Luis: Tôi đã đăng một phân tích dưới đây, nhưng nếu nó thỏa mãn bạn, xin vui lòng cho celion câu trả lời - tất cả những gì anh ấy nói là chính xác, tất cả những gì tôi đã làm là chạy GCC một vài lần.

12

Tôi nghi ngờ rằng thông tin về Xbox 360 và PS3 đặc biệt sẽ đứng sau các bức tường chỉ dành cho nhà phát triển được cấp phép, giống như hầu hết các chi tiết cấp thấp. Tuy nhiên, chúng ta có thể xây dựng một chương trình x86 tương đương và tháo rời nó để có được một ý tưởng chung.

Trước tiên, hãy xem những gì chi phí mở rộng không dấu:

unsigned char x = 1;
unsigned int y = 1;
unsigned int z;
z = x;
z = y;

Phần có liên quan tháo rời thành (sử dụng GCC 4.4.5):

    z = x;
  27:   0f b6 45 ff             movzbl -0x1(%ebp),%eax
  2b:   89 45 f4                mov    %eax,-0xc(%ebp)
    z = y;
  2e:   8b 45 f8                mov    -0x8(%ebp),%eax
  31:   89 45 f4                mov    %eax,-0xc(%ebp)

Về cơ bản là giống nhau - trong một trường hợp chúng ta di chuyển một byte, trong trường hợp khác chúng ta di chuyển một từ. Kế tiếp:

signed char x = 1;
signed int y = 1;
signed int z;
z = x;
z = y;

Trở thành:

   z = x;
  11:   0f be 45 ff             movsbl -0x1(%ebp),%eax
  15:   89 45 f4                mov    %eax,-0xc(%ebp)
    z = y;
  18:   8b 45 f8                mov    -0x8(%ebp),%eax
  1b:   89 45 f4                mov    %eax,-0xc(%ebp)

Vì vậy, chi phí của phần mở rộng dấu hiệu là bất cứ giá nào movsblthay vì movzbl- mức hướng dẫn phụ. Về cơ bản, điều đó không thể định lượng được trên các bộ xử lý hiện đại do cách thức hoạt động của các bộ xử lý hiện đại. Mọi thứ khác, từ tốc độ bộ nhớ đến bộ nhớ đệm cho đến những gì trong đường ống trước đó, sẽ thống trị thời gian chạy.

Trong khoảng 10 phút để tôi viết các bài kiểm tra này, tôi có thể dễ dàng tìm thấy một lỗi hiệu suất thực sự và ngay khi tôi bật bất kỳ mức tối ưu hóa trình biên dịch nào, mã sẽ không thể nhận ra đối với các tác vụ đơn giản như vậy.

Đây không phải là Stack Overflow, vì vậy tôi hy vọng không ai ở đây sẽ tuyên bố vi mô hóa không thành vấn đề. Các trò chơi thường hoạt động trên dữ liệu rất lớn và rất số, do đó, việc chú ý cẩn thận đến việc phân nhánh, diễn xuất, lập lịch, căn chỉnh cấu trúc, v.v có thể mang lại những cải tiến rất quan trọng. Bất cứ ai đã dành nhiều thời gian để tối ưu hóa mã PPC có thể có ít nhất một câu chuyện kinh dị về các cửa hàng tải trọng. Nhưng trong trường hợp này, nó thực sự không thành vấn đề. Kích thước lưu trữ của loại số nguyên của bạn không ảnh hưởng đến hiệu suất, miễn là nó được căn chỉnh và vừa với một thanh ghi.


2
(CW vì đây thực sự chỉ là một lời nhận xét về câu trả lời celion, và vì tôi tò mò thay đổi những gì mã của người có thể có để làm cho nó minh họa hơn.)

Thông tin về CPU PS3 là có sẵn và hợp pháp, vì vậy thảo luận về các nội dung CPU liên quan đến PS3 không phải là vấn đề. Cho đến khi Sony loại bỏ hỗ trợ OtherOS, bất kỳ ai cũng có thể gắn Linux trên PS3 và lập trình nó. GPU đã vượt quá giới hạn, nhưng CPU (bao gồm cả SPE) vẫn ổn. Ngay cả khi không có OtherOS hỗ trợ, bạn vẫn có thể dễ dàng lấy GCC thích hợp và xem mã gen là như thế nào.
JasonD

@Jason: Tôi đã gắn cờ bài đăng của mình là CW vì vậy nếu ai đó làm điều này họ có thể cung cấp thông tin. Tuy nhiên, bất kỳ ai có quyền truy cập vào trình biên dịch GameOS chính thức của Sony - đây thực sự là vấn đề duy nhất - có lẽ bị cấm làm như vậy.

Trên thực tế, số nguyên đã ký đắt hơn trên PPC IIRC. Nó có một hiệu suất rất nhỏ, nhưng nó có ... cũng có rất nhiều chi tiết PPU / SPU của PS3 ở đây: jheriko-rtw.blogspot.co.uk/2011/07/ps3-ppuspu-docs.html và tại đây: jheriko-rtw.blogspot.co.uk/2011/03/ppc-in cản-set.html . Bạn có tò mò trình biên dịch GameOS này là gì không? Đó có phải là trình biên dịch GCC hay là một trong số đó không? khác với những điều đã được đề cập, các so sánh đã ký có một chi phí chung khi nói về việc tối ưu hóa các vòng lặp trong cùng. Tôi không có quyền truy cập vào các tài liệu mô tả điều này mặc dù - và ngay cả khi tôi đã làm ...
jheriko

4

Các hoạt động số nguyên đã ký có thể đắt hơn trên hầu hết các kiến ​​trúc. Ví dụ: chia cho một hằng số nhanh hơn khi không dấu, ví dụ:

unsigned foo(unsigned a) { return a / 1024U; }

sẽ được tối ưu hóa để:

unsigned foo(unsigned a) { return a >> 10; }

Nhưng...

int foo(int a) { return a / 1024; }

sẽ tối ưu hóa để:

int foo(int a) {
  return (a + 1023 * (a < 0)) >> 10;
}

hoặc trên các hệ thống mà chi nhánh rẻ,

int foo(int a) {
  if (a >= 0) return a >> 10;
  else return (a + 1023) >> 10;
}

Modulo cũng vậy. Điều này cũng đúng với những người không có quyền hạn 2 (nhưng ví dụ phức tạp hơn). Nếu kiến ​​trúc của bạn không có phân chia phần cứng (ví dụ như hầu hết ARM), các phân chia không dấu của các hằng số cũng nhanh hơn.

Nói chung, nói với trình biên dịch rằng các số âm không thể dẫn đến sẽ tối ưu hóa các biểu thức, đặc biệt là các số được sử dụng để chấm dứt vòng lặp và các điều kiện khác.

Đối với các kích thước khác nhau, có một tác động nhỏ nhưng bạn phải cân nhắc điều đó với việc di chuyển ít bộ nhớ hơn. Ngày nay, bạn có thể kiếm được nhiều hơn từ việc truy cập ít bộ nhớ hơn số tiền bạn mất từ ​​việc mở rộng kích thước. Tại thời điểm đó bạn đang rất tối ưu hóa vi mô.


Tôi đã chỉnh sửa mã được tối ưu hóa của bạn để phản ánh rõ hơn những gì GCC thực sự tạo ra, ngay cả trên -O0. Có một chi nhánh đã gây hiểu nhầm khi một bài kiểm tra + cho phép bạn làm điều đó không có chi nhánh.

2
Trên x86, có thể. Trên ARMv7, nó chỉ được thực hiện có điều kiện.
John Ripley

3

Các hoạt động với int đã ký hoặc không dấu có cùng chi phí trên các bộ xử lý hiện tại (x86_64, x86, powerpc, arm). Trên bộ xử lý 32 bit, u32, u16, u8 s32, s16, s8 phải giống nhau. Bạn có thể có hình phạt với sự sắp xếp xấu.

Nhưng chuyển đổi int thành float hoặc float thành int là một hoạt động tốn kém. Bạn có thể dễ dàng tìm thấy triển khai tối ưu hóa (SSE2, neon ...).

Điểm quan trọng nhất có lẽ là truy cập bộ nhớ. Nếu dữ liệu của bạn không phù hợp với bộ đệm L1 / L2, bạn sẽ mất nhiều chu kỳ hơn chuyển đổi.


2

Jon Purdy nói ở trên (tôi không thể nhận xét) rằng không dấu có thể chậm hơn vì nó không thể tràn. Tôi không đồng ý, số học không dấu là số đơn giản moular số học modulo 2 với số bit trong từ. Các hoạt động đã ký về nguyên tắc có thể bị tràn, nhưng chúng thường bị tắt.

Đôi khi bạn có thể thực hiện các thao tác thông minh (nhưng không dễ đọc) như gói hai hoặc nhiều mục dữ liệu vào một int và nhận nhiều thao tác trên mỗi lệnh (số học bỏ túi). Nhưng bạn phải hiểu những gì bạn đang làm. Tất nhiên MMX cho phép bạn làm điều này một cách tự nhiên. Nhưng đôi khi sử dụng kích thước từ được hỗ trợ CTNH lớn nhất và đóng gói dữ liệu theo cách thủ công sẽ cho bạn triển khai nhanh nhất.

Hãy cẩn thận về căn chỉnh dữ liệu. Trên hầu hết các triển khai CTNH, tải không được phân bổ và các cửa hàng chậm hơn. Căn chỉnh tự nhiên, có nghĩa là để nói một từ 4byte, địa chỉ là bội số của bốn và tám địa chỉ từ phải là bội số của tám byte. Điều này mang đến SSE (128 bit ủng hộ căn chỉnh 16byte). AVX sẽ sớm mở rộng các kích thước đăng ký "vectơ" này thành 256 bit sau đó là 512 bit. Và tải / cửa hàng được căn chỉnh sẽ nhanh hơn so với tải không được phân bổ. Đối với các chuyên viên máy tính CTNH, một hoạt động bộ nhớ không được phân bổ có thể mở rộng những thứ như đường dẫn bộ nhớ cache và thậm chí cả trang, mà CTNH phải cẩn thận.


1

Tốt hơn một chút là sử dụng các số nguyên đã ký cho các chỉ mục vòng lặp, vì tràn tràn đã ký không được xác định trong C, do đó trình biên dịch sẽ cho rằng các vòng lặp như vậy có ít trường hợp góc hơn. Điều này được kiểm soát bởi "-fstrict-overflow" của gcc (được bật theo mặc định) và hiệu ứng có thể khó nhận thấy nếu không đọc đầu ra lắp ráp.

Ngoài ra, x86 hoạt động tốt hơn nếu bạn không trộn các loại, vì nó có thể sử dụng toán hạng bộ nhớ. Nếu nó phải chuyển đổi các loại (ký hiệu hoặc phần mở rộng bằng không) có nghĩa là tải rõ ràng và việc sử dụng một thanh ghi.

Gắn bó với int cho các biến cục bộ và hầu hết điều này sẽ xảy ra theo mặc định.


0

Như celion chỉ ra, chi phí chuyển đổi giữa ints và float phần lớn liên quan đến việc sao chép và chuyển đổi các giá trị giữa các thanh ghi. Chi phí duy nhất của các số nguyên không dấu trong và bản thân chúng đến từ hành vi đóng gói được bảo đảm của chúng, đòi hỏi một số lượng kiểm tra tràn nhất định trong mã được biên dịch.

Về cơ bản không có chi phí chuyển đổi giữa các số nguyên đã ký và không dấu. Các kích thước khác nhau của số nguyên thể (vô hạn) nhanh hơn hoặc chậm hơn để truy cập tùy thuộc vào nền tảng. Nói chung, kích thước của số nguyên gần nhất với kích thước từ của nền tảng sẽ nhanh nhất để truy cập, nhưng sự khác biệt hiệu suất tổng thể phụ thuộc vào nhiều yếu tố khác, đáng chú ý nhất là kích thước bộ đệm: nếu bạn sử dụng uint64_tkhi tất cả những gì bạn cần là uint32_t, nó có thể có thể ít dữ liệu của bạn sẽ phù hợp với bộ đệm cùng một lúc và bạn có thể phải chịu một số chi phí tải.

Dù vậy, hơi quá khi nghĩ về điều này. Nếu bạn sử dụng các loại phù hợp với dữ liệu của mình, mọi thứ sẽ hoạt động hoàn toàn tốt và lượng năng lượng đạt được bằng cách chọn các loại dựa trên kiến ​​trúc dù sao cũng không đáng kể.


Kiểm tra tràn nào bạn đang đề cập đến? Trừ khi bạn có nghĩa là một cấp độ thấp hơn trình biên dịch chương trình, mã để thêm hai số nguyên là giống hệt nhau trên hầu hết các hệ thống và không thực sự dài hơn trên một số ít sử dụng ví dụ như cường độ ký hiệu. Chỉ khác nhau.

@JoeWreschnig: Chết tiệt. Tôi dường như không thể tìm thấy nó, nhưng tôi biết tôi đã thấy các ví dụ về kế toán đầu ra của trình biên dịch mã khác nhau cho hành vi đóng gói được xác định, ít nhất là trên các nền tảng nhất định. Bài đăng liên quan duy nhất tôi có thể tìm thấy: stackoverflow.com/questions/4712315/ từ
Jon Purdy

Đầu ra trình biên dịch mã khác nhau cho hành vi đóng gói khác nhau là do trình biên dịch có thể thực hiện tối ưu hóa trong trường hợp đã ký, ví dụ: nếu b> 0 thì a + b> a, vì tràn không được xác định (và do đó không thể dựa vào). Đó thực sự là một tình huống hoàn toàn khác.
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.