Çekirdek'teki olası ve olası çağrılar arasındaki fark nedir?


11

Çekirdek'teki olası ve olası çağrılar arasındaki fark nedir? Çekirdek kaynağında arama yaparken bu ifadeleri buldum.

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

Birisi ona ışık tutabilir mi?


Bu gerçekten bir programlama sorusu, Stack OVerflow için daha uygun .
Gilles 'SO- kötü olmayı bırak'

Yanıtlar:


14

Bunlar GCC için derleyici ipuçlarıdır. Şartlı olarak derleyiciye bir dalın alınıp alınmayacağını söylemek için kullanılırlar. Derleyicinin kodu en sık sonuç için en uygun şekilde bırakmasına yardımcı olabilir.

Bu şekilde kullanılırlar:

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();
}

Çok dikkatli kullanılmalıdır (örn. Gerçek şube profili sonuçlarına dayanarak). Yanlış bir ipucu performansı düşürebilir (belli ki).

Kodun nasıl optimize edilebileceğine ilişkin bazı örnekler aranarak kolayca bulunabilir GCC __builtin_expect. Bu blog yazısı gcc optimizasyonu: örneğin __builtin_expect , bununla birlikte bir demontajı detaylandırıyor.

Yapılabilecek optimizasyon türleri işlemciye özeldir. Genel fikir, çoğu zaman işlemcilerin her yerde dallanmadığı / atlamadığı durumlarda kodu daha hızlı çalıştıracağıdır. Ne kadar doğrusal ve dallar ne kadar öngörülebilir olursa, o kadar hızlı çalışır. (Bu özellikle derin boru hatlarına sahip işlemciler için geçerlidir.)

Böylece derleyici kodu, örneğin hedef CPU'nun tercih ettiği şeyse, en olası dalın bir sıçrama içermeyeceği şekilde yayar.


Tek boynuzlu atlar ne anlama geliyor ? Teknik bir terim mi yoksa sadece bir dolgu maddesi mi?
Sen

Karışıklığı önlemek için tek boynuzlu atları çıkardım.
Mat

Eğer üzerinde durmak misiniz denemek ve durum için kod düzeni optimal olan yapacak derleyici ? Bunu nasıl yaptığını bilmek istiyorum.
Sen

bu konuda biraz bilgi ekledi. kodu optimize etmenin genel bir yolu yoktur, hepsi işlemciye bağlıdır.
Mat

2

GCC 4.8'in onunla ne yaptığını görelim

Beklemeden

#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;
}

GCC 4.8.2 x86_64 Linux ile derleyin ve kaynak kodunu çözün:

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

Çıktı:

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

Bellekte talimat sırası değişmedi: İlk printfve sonra putsve retqdönüş.

Beklenti ile

Şimdi aşağıdakilerle değiştirin if (i):

if (__builtin_expect(i, 0))

ve elde ederiz:

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>

printf(Derlenmiş __printf_chksonra), fonksiyon sonuna taşındı putsdiğer yanıtlar tarafından belirtildiği gibi dal tahmini geliştirmek ve geri döner.

Yani temel olarak aynı:

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

Bu optimizasyon ile yapılmadı -O0.

Ancak __builtin_expect, CPU'ların onsuzdan daha hızlı çalışan bir örnek yazma konusunda iyi şanslar , CPU'lar o günlerde gerçekten akıllı . Benim saf denemelerim burada .

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

C ++ 20, bu C ++ yerleşiklerini standartlaştırmıştır: /programming/51797959/how-to-use-c20s-likely-unlikely-attribute-in-if-else-statement Muhtemelen (a pun!) aynı şeyi yapın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.