Sự khác biệt giữa các cuộc gọi có khả năng và không thể xảy ra trong Kernel là gì?


11

Giữa các cuộc gọi có khả năng và không thể xảy ra trong Kernel là gì. Trong khi tìm kiếm thông qua nguồn kernel tôi đã tìm thấy những tuyên bố này.

# define likely(x)      __builtin_expect(!!(x), 1)
# define unlikely(x)    __builtin_expect(!!(x), 0)

Ai đó có thể làm sáng tỏ nó?


Đây thực sự là một câu hỏi lập trình, phù hợp hơn với Stack OVerflow .
Gilles 'SO- ngừng trở nên xấu xa'

Câu trả lời:


14

Chúng là những gợi ý về trình biên dịch cho GCC. Chúng được sử dụng trong các điều kiện để báo cho trình biên dịch xem một nhánh có khả năng bị lấy hay không. Nó có thể giúp trình biên dịch đặt mã theo cách tối ưu cho kết quả thường xuyên nhất.

Chúng được sử dụng như thế này:

if (likely(some_condition)) {
  // the compiler will try and make the code layout optimal for the case
  // where some_condition is true, i.e. where this block is run
  most_likely_action();
} else {
  // this block is less frequently used
  corner_case();
}

Nó nên được sử dụng hết sức cẩn thận (tức là dựa trên kết quả hồ sơ chi nhánh thực tế). Một gợi ý sai có thể làm giảm hiệu suất (rõ ràng).

Một số ví dụ về cách mã hóa có thể được tối ưu hóa dễ dàng được tìm thấy bằng cách tìm kiếm GCC __builtin_expect. Bài đăng blog này tối ưu hóa gcc: __builtin_Exect chẳng hạn, chi tiết việc tháo gỡ với nó.

Các loại tối ưu hóa có thể được thực hiện là rất cụ thể của bộ xử lý. Ý tưởng chung là thông thường, các bộ xử lý sẽ chạy mã nhanh hơn nếu nó không phân nhánh / nhảy khắp nơi. Nó càng tuyến tính và các nhánh càng dễ dự đoán thì nó sẽ chạy càng nhanh. (Điều này đặc biệt đúng đối với các bộ xử lý có đường ống sâu chẳng hạn.)

Vì vậy, trình biên dịch sẽ phát ra mã sao cho nhánh có khả năng nhất sẽ không liên quan đến bước nhảy nếu đó là thứ mà CPU mục tiêu thích, chẳng hạn.


Kỳ lân có nghĩa là gì? Nó là một thuật ngữ kỹ thuật hay chỉ là một phụ?
Sen

Tôi gỡ con kỳ lân để tránh nhầm lẫn.
Mat

Bạn có thể vui lòng giải thích về trình biên dịch sẽ thử và làm cho bố cục mã tối ưu cho trường hợp không? Tôi muốn biết làm thế nào nó làm điều đó.
Sen

thêm một chút thông tin về điều đó không có cách tối ưu hóa mã chung, tất cả đều phụ thuộc vào bộ xử lý.
Mat

2

Hãy dịch ngược để xem GCC 4.8 làm gì với nó

Không mong đợi

#include "stdio.h"
#include "time.h"

int main() {
    /* Use time to prevent it from being optimized away. */
    int i = !time(NULL);
    if (i)
        printf("%d\n", i);
    puts("a");
    return 0;
}

Biên dịch và dịch ngược với GCC 4.8.2 x86_64 Linux:

gcc -c -O3 -std=gnu11 main.c
objdump -dr main.o

Đầu ra:

0000000000000000 <main>:
   0:       48 83 ec 08             sub    $0x8,%rsp
   4:       31 ff                   xor    %edi,%edi
   6:       e8 00 00 00 00          callq  b <main+0xb>
                    7: R_X86_64_PC32        time-0x4
   b:       48 85 c0                test   %rax,%rax
   e:       75 14                   jne    24 <main+0x24>
  10:       ba 01 00 00 00          mov    $0x1,%edx
  15:       be 00 00 00 00          mov    $0x0,%esi
                    16: R_X86_64_32 .rodata.str1.1
  1a:       bf 01 00 00 00          mov    $0x1,%edi
  1f:       e8 00 00 00 00          callq  24 <main+0x24>
                    20: R_X86_64_PC32       __printf_chk-0x4
  24:       bf 00 00 00 00          mov    $0x0,%edi
                    25: R_X86_64_32 .rodata.str1.1+0x4
  29:       e8 00 00 00 00          callq  2e <main+0x2e>
                    2a: R_X86_64_PC32       puts-0x4
  2e:       31 c0                   xor    %eax,%eax
  30:       48 83 c4 08             add    $0x8,%rsp
  34:       c3                      retq

Thứ tự lệnh trong bộ nhớ không thay đổi: đầu tiên printfvà sau đó putsretqtrả về.

Với mong đợi

Bây giờ thay thế if (i)bằng:

if (__builtin_expect(i, 0))

và chúng tôi nhận được:

0000000000000000 <main>:
   0:       48 83 ec 08             sub    $0x8,%rsp
   4:       31 ff                   xor    %edi,%edi
   6:       e8 00 00 00 00          callq  b <main+0xb>
                    7: R_X86_64_PC32        time-0x4
   b:       48 85 c0                test   %rax,%rax
   e:       74 11                   je     21 <main+0x21>
  10:       bf 00 00 00 00          mov    $0x0,%edi
                    11: R_X86_64_32 .rodata.str1.1+0x4
  15:       e8 00 00 00 00          callq  1a <main+0x1a>
                    16: R_X86_64_PC32       puts-0x4
  1a:       31 c0                   xor    %eax,%eax
  1c:       48 83 c4 08             add    $0x8,%rsp
  20:       c3                      retq
  21:       ba 01 00 00 00          mov    $0x1,%edx
  26:       be 00 00 00 00          mov    $0x0,%esi
                    27: R_X86_64_32 .rodata.str1.1
  2b:       bf 01 00 00 00          mov    $0x1,%edi
  30:       e8 00 00 00 00          callq  35 <main+0x35>
                    31: R_X86_64_PC32       __printf_chk-0x4
  35:       eb d9                   jmp    10 <main+0x10>

Các printf(biên soạn để __printf_chk) đã được chuyển đến tận cùng của hàm, sau putsvà sự trở lại để cải thiện dự đoán rẽ nhánh như đã đề cập bởi câu trả lời khác.

Vì vậy, về cơ bản nó giống như:

int i = !time(NULL);
if (i)
    goto printf;
puts:
puts("a");
return 0;
printf:
printf("%d\n", i);
goto puts;

Tối ưu hóa này đã không được thực hiện với -O0.

Nhưng may mắn khi viết một ví dụ chạy nhanh __builtin_expecthơn mà không có, CPU thực sự rất thông minh ngày đó . Những nỗ lực ngây thơ của tôi đang ở đây .

C ++ 20 [[likely]][[unlikely]]

C ++ 20 đã chuẩn hóa các bản dựng sẵn C ++ đó: /programming/51797959/how-to-use-c20s-labilities-unlabilities-attribution-in-if-else-statement Họ sẽ có khả năng (a chơi chữ!) làm điều tương tự

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.