Linux çekirdeğindeki olası / olası olmayan makrolar nasıl çalışır ve faydaları nelerdir?


349

Linux çekirdeğinin bazı bölümlerini araştırıyorum ve şu şekilde aramalar buldum:

if (unlikely(fd < 0))
{
    /* Do something */
}

veya

if (likely(!err))
{
    /* Do something */
}

Bunların tanımını buldum:

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

Optimizasyon için olduklarını biliyorum, ama nasıl çalışırlar? Ve bunları kullanmaktan ne kadar performans / boyut azalması beklenebilir? Ve en azından darboğaz kodunda (elbette kullanıcı alanında) uğraşmaya değer (ve muhtemelen taşınabilirliği kaybetme).


7
Bu gerçekten Linux çekirdeğine veya makrolara özgü değil, derleyici optimizasyonu. Bunu yansıtacak şekilde yeniden etiketlenmeli mi?
Cody Brocious

11
Her Programcı'nın Bellek hakkında bilmesi gereken makale (s. 57) ayrıntılı bir açıklama içermektedir.
Torsten Marek

2
Ayrıca bakınızBOOST_LIKELY
Ruggero Turra


13
Taşınabilirlik sorunu yok. Bu tür ipuçlarını desteklemeyen platformlarda #define likely(x) (x)ve #define unlikely(x) (x)platformlarda önemsiz şeyler yapabilirsiniz.
David Schwartz

Yanıtlar:


329

Derleyiciye, dal tahmininin bir atlama komutunun "olası" tarafını desteklemesine neden olacak talimatlar yayınlaması için ipucu veriyorlar. Bu büyük bir kazanç olabilir, tahmin doğruysa atlama talimatının temelde ücretsiz olduğu ve sıfır döngü alacağı anlamına gelir. Öte yandan tahmin yanlışsa, işlemci boru hattının yıkanması gerektiği anlamına gelir ve birkaç döngüye mal olabilir. Tahmin çoğu zaman doğru olduğu sürece, bu performans için iyi olacaktır.

Tüm bu performans optimizasyonları gibi, kodun gerçekten bir darboğazda olduğundan ve muhtemelen mikro doğası nedeniyle sıkı bir döngüde çalıştığından emin olmak için kapsamlı profil oluşturduktan sonra yapmalısınız. Genellikle Linux geliştiricileri oldukça deneyimlidir, bu yüzden bunu yapabileceklerini hayal ederdim. Sadece gcc'yi hedefledikleri için taşınabilirlikle çok fazla ilgilenmiyorlar ve üretmelerini istedikleri montaj hakkında çok yakın bir fikirleri var.


3
Bu makrolar çoğunlukla hata kontrolü için kullanılmıştır. Çünkü hata normal işlemden daha az olasıdır. Birkaç kişi en çok kullanılan yaprağa karar vermek için profil oluşturma veya hesaplama yapar ...
gavenkoa

51
Parçayla ilgili olarak "[...]that it is being run in a tight loop", birçok CPU'nun bir dal tahmincisi vardır , bu nedenle bu makroların kullanılması ilk kodun çalıştırılmasına veya geçmiş tablosunun dallanma tablosuna aynı dizine sahip farklı bir dal tarafından üzerine yazılmasına yardımcı olur. Sıkı bir döngüde ve bir dalın çoğu zaman bir yol gittiğini varsayarsak, dal tahmincisi büyük olasılıkla doğru dalı çok hızlı bir şekilde tahmin etmeye başlayacaktır. - bilgiçlikteki arkadaşın.
Ross Rogers

8
@RossRogers: Gerçekte olan şey, derleyicinin dalları düzenlemesi, böylece ortak durum alınmayan. Bu, şube tahmini işe yaradığında bile daha hızlıdır. Alınan dallar, mükemmel tahmin edildiklerinde bile talimat getirme ve kod çözme için sorunludur. Bazı CPU'lar, geçmiş tablolarında bulunmayan dalları, genellikle ileri dallar için alınmadığını varsayarak, statik olarak tahmin eder. Intel CPU'lar bu şekilde çalışmaz: öngörücü tablo girişinin bu dal için olduğunu kontrol etmeye çalışmazlar, yine de kullanırlar. Sıcak bir şube ve bir soğuk şube aynı girişi
Peter Cordes

12
Bu cevap çoğunlukla eski olduğu için ana iddia, şube tahminine yardımcı olmasıdır ve @PeterCordes'un işaret ettiği gibi, modern donanımların çoğunda örtülü veya açık statik şube tahmini yoktur. Aslında ipucu derleyici tarafından statik dal ipuçlarını veya başka bir optimizasyon türünü içeriyorsa kodu optimize etmek için kullanılır. Günümüzde çoğu mimaride önemli olan "sıcak yolların bitişik hale getirilmesi, sıcak yolun daha iyi planlanması, yavaş yolun boyutunun en aza indirilmesi, sadece beklenen yolun vektörleştirilmesi, vb."
Gibi

3
@BeeOnRope, önbellek önceden getirme ve kelime boyutu nedeniyle, bir programı doğrusal olarak çalıştırmanın hala bir avantajı vardır. Bir sonraki bellek konumu zaten getirilecek ve önbellekte, şube hedefi belki veya değil. 64 bit CPU ile bir seferde en az 64 bit alırsınız. DRAM serpiştirmesine bağlı olarak, yakalanan 2x 3x veya daha fazla bit olabilir.
Bryce

88

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

olmadan __builtin_expect

#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üş.

İle __builtin_expect

Ş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 main() {
    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'lardan daha hızlı çalışan bir örnek yazma konusunda iyi şanslar , CPU'lar bu günlerde gerçekten akıllı . Saf denemelerim burada .

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

C ++ 20, bu C ++ yerleşiklerini standartlaştırmıştır: C ++ 20'nin if-else deyiminde olası / olası olmayan özniteliği nasıl kullanılır ?


71

Bunlar derleyiciye bir dalın hangi yoldan gidebileceği hakkında ipuçları veren makrolardır. Makrolar, varsa GCC'ye özgü uzantılara genişler.

GCC bunları şube tahminini optimize etmek için kullanır. Örneğin, aşağıdakine benzer bir şeyiniz varsa

if (unlikely(x)) {
  dosomething();
}

return x;

Sonra bu kodu daha çok benzer bir şekilde yeniden yapılandırabilir:

if (!x) {
  return x;
}

dosomething();
return x;

Bunun yararı, işlemci ilk kez bir dal aldığında, önemli bir ek yükün bulunmasıdır, çünkü spekülatif olarak kodun ileride yüklenmesi ve yürütülmesi olabilir. Şubeyi alacağını belirlediğinde, bunu geçersiz kılmalı ve şube hedefinden başlamalıdır.

Modern işlemcilerin çoğunda artık bir tür dal tahmini vardır, ancak bu yalnızca daha önce daldan geçtiğinizde yardımcı olur ve dal hala dal tahmin önbelleğinde bulunur.

Derleyici ve işlemcinin bu senaryolarda kullanabileceği başka stratejiler de vardır. Şube tahmin edicilerinin nasıl çalıştığı hakkında daha fazla bilgiyi Wikipedia'da bulabilirsiniz: http://en.wikipedia.org/wiki/Branch_predictor


3
Ayrıca, olası kod snippet'lerini sıcak yoldan uzak tutarak buzdağı kapladığı alanı etkiler.
fche

2
Daha doğrusu, bunu gototekrar etmeden s ile yapabilir return x: stackoverflow.com/a/31133787/895245
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

7

Derleyicinin, donanımı desteklediği yerlerde uygun dal ipuçlarını yaymasına neden olurlar. Bu genellikle komut opcode'unda birkaç bit döndürmek anlamına gelir, bu nedenle kod boyutu değişmez. CPU, tahmin edilen konumdan talimatları almaya başlar ve boru hattını yıkar ve şubeye ulaşıldığında yanlış olduğu ortaya çıkar; ipucunun doğru olması durumunda, dal daha hızlı hale gelecektir - tam olarak ne kadar hızlı donanıma bağlı olacaktır; ve bunun kodun performansını ne kadar etkilediği, zaman ipucunun ne kadarının doğru olduğuna bağlı olacaktır.

Örneğin, bir PowerPC CPU üzerinde ipucu vermeyen bir dal 16 döngü, doğru ipucu 8 ve yanlış ipucu 24 olabilir. En içteki döngülerde iyi ipucu muazzam bir fark yaratabilir.

Taşınabilirlik gerçekten bir sorun değildir - muhtemelen tanım platform başına bir başlıktadır; statik dal ipuçlarını desteklemeyen platformlar için hiçbir şeye "olası" ve "olası" olmayanları tanımlayabilirsiniz.


3
Kayıt için x86, dal ipuçları için ek alan kaplar. Uygun ipucunu belirtmek için dallarda bir bayt önek olması gerekir. Bununla birlikte, imalamanın İyi Bir Şey (TM) olduğunu kabul etti.
Cody Brocious

2
Dang CISC CPU'lar ve değişken uzunluktaki talimatları;)
moonshadow 20:08

3
Dang RISC CPU'lar - 15 baytlık talimatlarımdan uzak
dur

7
@CodyBrocious: P4 ile dal ipucu verildi, ancak P4 ile birlikte terk edildi. Diğer tüm x86 işlemciler bu önekleri yok sayar (çünkü önekler her zaman anlamsız oldukları bağlamlarda yok sayılır). Bu makrolar gcc'nin aslında x86'da şube ipucu önekleri yaymasına neden olmaz . Onlar hızlı yolda daha az alınan dalları ile fonksiyonunuzu düzenlemek için gcc almak yardımcı olur.
Peter Cordes

5
long __builtin_expect(long EXP, long C);

Bu yapı derleyiciye EXP ifadesinin büyük olasılıkla C değerine sahip olacağını bildirir. Dönüş değeri EXP'dir. __builtin_expect , koşullu bir ifadede kullanılmalıdır. Hemen hemen tüm durumlarda, boole ifadeleri bağlamında kullanılacaktır, bu durumda iki yardımcı makro tanımlamak çok daha uygundur:

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

Bu makrolar daha sonra olduğu gibi kullanılabilir

if (likely(a > 1))

Referans: https://www.akkadia.org/drepper/cpumemory.pdf


1
Başka bir cevaba yapılan bir yorumda istendiği gibi - makrolardaki çift inversiyonun nedeni nedir (yani neden __builtin_expect(!!(expr),0)sadece kullanmak yerine __builtin_expect((expr),0)?)
Michael Firth

1
@MichaelFirth "çift inversiyon" !!, a bool. Bazı insanlar bunu bu şekilde yazmayı sever.
Ben XO

2

(genel yorum - diğer cevaplar ayrıntıları kapsar)

Kullanarak taşınabilirliği kaybetmeniz için hiçbir neden yoktur.

Her zaman, diğer derleyicilerle diğer platformlarda derlemenizi sağlayacak basit bir nil efektli "satır içi" veya makro oluşturma seçeneğiniz vardır.

Başka platformlardaysanız optimizasyondan yararlanamazsınız.


1
Taşınabilirliği kullanmazsınız - onları desteklemeyen platformlar, onları boş dizelere genişleyecek şekilde tanımlar.
sharptooth

2
Sanırım ikiniz aslında birbirinizle hemfikirsiniz - bu sadece kafa karıştırıcı bir şekilde ifade edildi. (Görünüşe göre, Andrew'un yorumu “taşınabilirliği kaybetmeden kullanabilirsiniz” diyor, ancak sharptooth “taşınabilir olmadığı için kullanma” dediğini ve itiraz ettiğini düşündü.)
Miral

2

Cody'nin yorumuna göre , bunun Linux ile ilgisi yok, ancak derleyiciye bir ipucu. Ne olacağı mimariye ve derleyici sürümüne bağlı olacaktır.

Linux'taki bu özellik sürücülerde biraz yanlış kullanılıyor. As osgx dışarı noktalarının sıcak özelliğinin semantik , herhangi hotveya coldbir blokta ile adlandırılan işlevi otomatik durum muhtemelen ya olmadığını ipucu olabilir. Örneğin, dump_stack()işaretlenmiş coldbu gereksiz yani,

 if(unlikely(err)) {
     printk("Driver error found. %d\n", err);
     dump_stack();
 }

Gelecek sürümleri gccbu ipuçlarına dayalı olarak bir işlevi seçici olarak satır içine alabilir. Ayrıca olmadığı boolean, ancak büyük olasılıkla olduğu gibi bir puan olduğu gibi öneriler de vardır . Genellikle, gibi bazı alternatif mekanizmaların kullanılması tercih edilmelidir cold. Sıcak yollardan başka hiçbir yerde kullanmak için hiçbir sebep yoktur. Bir derleyicinin bir mimaride yapacağı başka bir mimaride tamamen farklı olabilir.


2

Birçok linux sürümünde, complier.h dosyasını / usr / linux / içinde bulabilirsiniz, sadece kullanım için dahil edebilirsiniz. Ve başka bir görüş, olası olmayan () muhtemelen () yerine daha yararlıdır, çünkü

if ( likely( ... ) ) {
     doSomething();
}

birçok derleyicide de optimize edilebilir.

Bu arada, kodun ayrıntı davranışını gözlemlemek istiyorsanız, basitçe aşağıdaki gibi yapabilirsiniz:

gcc -c test.c objdump -d test.o> obj.s

Ardından obj.s'yi açın, cevabı bulabilirsiniz.


1

Bunlar, dallarda ipucu önekleri oluşturmak için derleyiciye ipuçları. X86 / x64 üzerinde, bir bayt alırlar, böylece her dal için en fazla bir bayt artış elde edersiniz. Performansa gelince, bu tamamen uygulamaya bağlıdır - çoğu durumda, bu günlerde işlemcideki dal tahmincisi onları görmezden gelir.

Düzenleme: Gerçekten yardımcı olabilecekleri bir yeri unuttum. Derleyicinin 'olası' yol için alınan dal sayısını azaltmak için kontrol-akış grafiğini yeniden sıralamasına izin verebilir. Bu, çoklu çıkış durumlarını kontrol ettiğiniz döngülerde belirgin bir iyileşme sağlayabilir.


10
gcc asla x86 şube ipucu üretmez - en azından tüm Intel CPU'lar zaten onları görmezden gelir. Yine de, satır içi ve döngü açmayı önleyerek olası olmayan bölgelerde kod boyutunu sınırlamaya çalışacaktır.
alex garip

1

Bunlar, programcının derleyiciye belirli bir ifadede en olası dallanma koşulunun ne olacağı hakkında bir ipucu vermesi için GCC işlevleridir. Bu, derleyicinin şube talimatlarını oluşturmasına izin verir, böylece en yaygın durum yürütmek için en az sayıda talimat alır.

Şube talimatlarının nasıl oluşturulduğu işlemci mimarisine bağlıdır.

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.