GCC'nin şube tahminini her zaman belirli bir yoldan gitmeye zorlaması için bir derleyici ipucu var mı?


118

Intel mimarileri için, GCC derleyicisine her zaman dal tahminini kodumda belirli bir şekilde zorlayan kod üretmesi talimatını vermenin bir yolu var mı? Intel donanımı bunu destekliyor mu? Diğer derleyiciler veya donanımlar ne olacak?

Bunu C ++ kodunda, hızlı koşmak istediğimi bildiğim ve yakın zamanda o şubeyi aldığında bile diğer şubenin alınması gerektiğinde yavaşlamayı umursamadığım durumlarda kullanırdım.

for (;;) {
  if (normal) { // How to tell compiler to always branch predict true value?
    doSomethingNormal();
  } else {
    exceptionalCase();
  }
}

Evdzhan Mustafa için takip eden bir soru olarak, ipucu, işlemcinin talimatla ilk karşılaşması için bir ipucu belirtebilir mi, sonraki tüm dal tahmini normal çalışıyor mu?


herhangi bir şey anormal hale gelirse bir istisna da atabilir (derleyiciden bağımsızdır)
Shep

Yanıtlar:


9

C ++ 20'den itibaren olası ve olası olmayan öznitelikler standartlaştırılmalıdır ve zaten g ++ 9'da desteklenmektedir . Yani burada tartışıldığı gibi yazabilirsiniz

if (a>b) {
  /* code you expect to run often */
  [[likely]] /* last statement */
}

örneğin aşağıdaki kodda başka bloğu için satır içi sayesinde alır [[unlikely]]eğer bloğunda

int oftendone( int a, int b );
int rarelydone( int a, int b );
int finaltrafo( int );

int divides( int number, int prime ) {
  int almostreturnvalue;
  if ( ( number % prime ) == 0 ) {
    auto k                         = rarelydone( number, prime );
    auto l                         = rarelydone( number, k );
    [[unlikely]] almostreturnvalue = rarelydone( k, l );
  } else {
    auto a            = oftendone( number, prime );
    almostreturnvalue = oftendone( a, a );
  }
  return finaltrafo( almostreturnvalue );
}

özniteliğin varlığını / yokluğunu karşılaştıran godbolt bağlantısı


Neden [[unlikely]]in ifvs [[likely]]in the kullanıyorsunuz else?
WilliamKF

Nedeni yok, sadece niteliğin gitmesi gereken yeri denedikten sonra bu takımyıldızın içinde kaldı.
pseyfert

Oldukça havalı. Metodun daha eski C ++ sürümleri için geçerli olmaması çok kötü.
Maxim Egorushkin

Fantastik ganimet bağlantısı
Lewis Kelsey

87

GCC, __builtin_expect(long exp, long c)bu tür bir özelliği sağlamak için işlevi destekler . Belgeleri buradan kontrol edebilirsiniz .

expKoşul nerede kullanılır ve cbeklenen değerdir. Örneğin, istersen

if (__builtin_expect(normal, 1))

Garip sözdizimi nedeniyle bu genellikle iki özel makro tanımlanarak kullanılır.

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

sadece görevi kolaylaştırmak için.

Aklında bulunsun:

  1. bu standart değil
  2. bir derleyici / işlemci dalı tahmin edicisi, bu tür şeylere karar verme konusunda muhtemelen sizden daha yeteneklidir, bu nedenle bu, erken bir mikro optimizasyon olabilir

3
Bir constexprişlev değil de bir makro göstermenizin bir nedeni var mı ?
Columbo

22
@Columbo: Bir constexprfonksiyonun bu makronun yerini alabileceğini sanmıyorum . ifDoğrudan inandığım ifadede olması gerekiyor . Aynı sebep assertasla bir constexprişlev olamaz .
Mooing Duck

1
@MooingDuck Katılıyorum, ancak iddia etmek için daha fazla neden var .
Shafik Yaghmour

7
@Columbo Bir makro kullanmanın bir nedeni, bunun C veya C ++ 'da bir makronun bir işlevden daha anlamsal olarak doğru olduğu birkaç yerden biri olmasıdır . İşlevi yalnızca nedeniyle optimizasyon çalışmalarına belirir ( olan bir optimizasyon: constexprdeğeri semantik, montaj uygulama-spesifik inlining değil hakkında sadece konuşur); kodun basit yorumu (satır içi yok) anlamsızdır. Bunun için bir işlev kullanmak için hiçbir neden yok.
Leushenko

2
@Leushenko __builtin_expectKendisinin bir optimizasyon ipucu olduğunu düşünün , bu yüzden kullanımını basitleştiren bir yöntemin optimizasyona bağlı olduğunu iddia etmek ... ikna edici değil. Ayrıca, constexprtanımlayıcıyı ilk etapta çalışmasını sağlamak için değil, sabit ifadelerde çalışmasını sağlamak için eklemedim . Ve evet, bir işlevi kullanmak için nedenler var. Örneğin, tüm ad alanımı gibi sevimli küçük bir adla kirletmek istemem likely. Örneğin LIKELY, bunun bir makro olduğunu vurgulamak ve çarpışmalardan kaçınmak için kullanmalıyım , ama bu çok çirkin.
Columbo

46

gcc sahip uzun __builtin_expect (uzun exp, uzun c) ( vurgu mayın ):

Derleyiciye dal tahmin bilgisi sağlamak için __builtin_expect'i kullanabilirsiniz. Genel olarak, programcılar programlarının gerçekte nasıl performans gösterdiğini tahmin etmede kötü şöhretli oldukları için bunun için gerçek profil geri bildirimini kullanmayı tercih etmelisiniz (-fprofile-yaylar) . Ancak, bu verilerin toplanmasının zor olduğu uygulamalar vardır.

Dönüş değeri, integral bir ifade olması gereken exp değeridir. Yerleşik olanın semantiği, exp == c'nin beklenmesidir. Örneğin:

if (__builtin_expect (x, 0))
   foo ();

x'in sıfır olmasını beklediğimiz için foo'yu çağırmayı beklemediğimizi gösterir. Exp için integral ifadelerle sınırlı olduğunuzdan, aşağıdaki gibi yapılar kullanmalısınız.

if (__builtin_expect (ptr != NULL, 1))
   foo (*ptr);

işaretçi veya kayan nokta değerlerini test ederken.

Belgelerin belirttiği gibi, gerçek profil geri bildirimini kullanmayı tercih etmelisiniz ve bu makale bunun pratik bir örneğini ve en azından kullanımdan ziyade en azından nasıl bir gelişme olduğunu gösteriyor __builtin_expect. Ayrıca , g ++ 'da profil kılavuzlu optimizasyonlar nasıl kullanılır? .

Ayrıca , bu özelliği kullanan () ve olası olmayan () çekirdek makroları hakkında bir Linux çekirdek yeni başlayanlar makalesi bulabiliriz :

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

!!Makroda kullanılana dikkat edin, bunun açıklamasını (koşul) yerine Neden !! (koşul) kullanmalıyız? .

Bu tekniğin Linux çekirdeğinde kullanılması, onu kullanmanın her zaman mantıklı olduğu anlamına gelmez. Bu sorudan yakın zamanda parametreyi derleme zamanı sabiti veya değişken olarak geçirirken fonksiyon performansı arasındaki farkı yanıtladığım , birçok elle haddelenmiş optimizasyon tekniğinin genel durumda çalışmadığını görebiliriz. Bir tekniğin etkili olup olmadığını anlamak için kodu dikkatli bir şekilde profillememiz gerekir. Çoğu eski teknik, modern derleyici optimizasyonları ile alakalı bile olmayabilir.

Not, yerleşikler taşınabilir olmasa da, __builtin_expect'i de destekler .

Ayrıca bazı mimarilerde bir fark yaratmayabilir .


Linux çekirdeği için yeterince iyi olan, C ++ 11 için yeterli değildir.
Maxim Egorushkin

@MaximEgorushkin not, aslında kullanılmasını önermiyorum, aslında ilk alıntı yaptığım gcc dokümantasyonu bu tekniği kullanmıyor bile. Cevabımın ana amacının, bu rotaya gitmeden önce alternatifleri dikkatlice değerlendirmek olduğunu söyleyebilirim.
Shafik Yaghmour

44

Hayır yok. (En azından modern x86 işlemcilerde.)

__builtin_expectdiğer yanıtlarda bahsedilen, gcc'nin montaj kodunu düzenleme şeklini etkiler. O değil doğrudan CPU şube öngorücunun etkiler. Elbette, kodun yeniden düzenlenmesinden kaynaklanan şube tahmini üzerinde dolaylı etkiler olacaktır. Ancak modern x86 işlemcilerinde, CPU'ya "bu dalın alındığını / alınmadığını varsayın" şeklinde bir talimat yoktur.

Daha fazla ayrıntı için şu soruya bakın: Intel x86 0x2E / 0x3E Önek Dal Tahmini gerçekten kullanılıyor mu?

Açık olmak gerekirse, __builtin_expectve / veya kullanılması -fprofile-arcs olabilir , hem kod düzeni ile şube ongorücüye ipuçları vererek kodun performansını artırmak (bakınız - Hizalama ve şube tahmini X86-64 montaj Performans optimizasyon ve ayrıca önbellek davranışını iyileştirmek) "olası" olmayan kodu "olası" koddan uzak tutarak.


9
Bu yanlış. X86'nın tüm modern sürümlerinde, varsayılan tahmin algoritması, ileri dalların alınmayacağını ve geriye dönük dalların alınacağını tahmin etmektir (bkz. Software.intel.com/en-us/articles/… ). Yani kodunuzu yeniden düzenleyerek siz yapabilirsiniz etkili bir CPU için bir ipucu vermek. GCC'nin kullandığınızda yaptığı tam olarak budur __builtin_expect.
Nemo

6
@Nemo, cevabımın ilk cümlesini okudun mu? Söylediğiniz her şey cevabım tarafından veya verilen bağlantılarda kapsanmaktadır. Cevabı "hayır" olan "şube tahminini her zaman belirli bir yöne gitmeye zorlayıp zorlayamayacağınız" sorusu ve diğer cevapların bu konuda yeterince açık olduğunu düşünmedim.
Artelius

4
Tamam, daha dikkatli okumalıydım. Bana öyle geliyor ki, bu cevap teknik olarak doğru ama faydasız çünkü soru soran açıkça aradığı için __builtin_expect. Yani bu sadece bir yorum olmalı. Ama bu yanlış değil, bu yüzden olumsuz oyumu kaldırdım.
Nemo

IMO işe yaramaz değil; Bu, CPU'ların ve derleyicilerin gerçekte nasıl çalıştığına dair yararlı bir açıklamadır ve bu seçeneklerle / bu seçenekler olmadan performans analizi ile ilgili olabilir. örneğin , çok yüksek bir dallanma yanlış tahmin oranına sahip olacak __builtin_expectşekilde ölçebileceğiniz bir test senaryosu oluşturmak için genellikle kullanamazsınız perf stat. Sadece şube düzenini etkiler . Ve BTW, Intel Sandybridge'den beri veya en azından Haswell statik tahmini pek / hiç kullanmaz; Eski bir takma ad olsa da olmasa da, BHT'de her zaman bir tahmin vardır. xania.org/201602/bpu-part-two
Peter Cordes

24

C ++ 11'de olası / olası olmayan makroları tanımlamanın doğru yolu şudur:

#define LIKELY(condition) __builtin_expect(static_cast<bool>(condition), 1)
#define UNLIKELY(condition) __builtin_expect(static_cast<bool>(condition), 0)

Bu yöntem, farklı olarak tüm C ++ sürümleriyle uyumludur [[likely]], ancak standart olmayan uzantıya dayanır __builtin_expect.


Bu makrolar bu şekilde tanımlandığında:

#define LIKELY(condition) __builtin_expect(!!(condition), 1)

Bu, ififadelerin anlamını değiştirebilir ve kodu bozabilir. Aşağıdaki kodu göz önünde bulundurun:

#include <iostream>

struct A
{
    explicit operator bool() const { return true; }
    operator int() const { return 0; }
};

#define LIKELY(condition) __builtin_expect((condition), 1)

int main() {
    A a;
    if(a)
        std::cout << "if(a) is true\n";
    if(LIKELY(a))
        std::cout << "if(LIKELY(a)) is true\n";
    else
        std::cout << "if(LIKELY(a)) is false\n";
}

Ve çıktısı:

if(a) is true
if(LIKELY(a)) is false

Gördüğünüz gibi, PEK kullanarak tanımı !!için bir döküm olarak boolsonlarını semantik if.

Buradaki mesele o değil operator int()ve operator bool()ilişkili olmalı. Hangisi iyi bir uygulamadır.

Bunun !!(x)yerine kullanmak static_cast<bool>(x), C ++ 11 bağlamsal dönüştürmeler için bağlamı kaybeder .


Not bağlamsal dönüşümler 2012 yılında bir defekt yoluyla geldi ve hatta 2014 yılının sonlarında halen uygulama sapma yoktu. Aslında bağlandığım vaka gcc için hala çalışmıyor gibi görünüyor.
Shafik Yaghmour

@ShafikYaghmour Bu, içerdiği bağlamsal dönüşümle ilgili ilginç bir gözlem switch, teşekkürler. Burada yer alan bağlamsal dönüştürme, yazıya kısmen vebool burada listelenen , switchbağlam içermeyen beş özel bağlamdır.
Maxim Egorushkin

Bu sadece C ++ 'yı etkiler, değil mi? Dolayısıyla, mevcut C projelerini kullanmak için gidip değiştirmek için hiçbir neden yok (_Bool)(condition), çünkü C'nin operatör aşırı yüklemesi yok.
Peter Cordes

2
Örneğinizde sadece kullandınız (condition), değil !!(condition). Her ikisi de truebunu değiştirdikten sonra (g ++ 7.1 ile test edildi). !!Booleanize etmek için kullandığınızda bahsettiğiniz sorunu gerçekten gösteren bir örnek oluşturabilir misiniz ?
Peter Cordes

3
Peter Cordes'in belirttiği gibi, "Bu makrolar bu şekilde tanımlandığında:" diyorsunuz ve ardından '!!' kullanarak bir makro gösterin, if ifadelerinin anlamını değiştirebilir ve kodu bozabilir. Şu kodu düşünün: " ... ve sonra '!!' kullanmayan bir kod gösterirsiniz. hiç - C ++ 11'den önce bile kırıldığı bilinen. Lütfen verilen makronun (!! kullanarak) yanlış gittiği bir örneği göstermek için cevabı değiştirin.
Carlo Ahşap

18

Diğer cevapların yeterince önerildiği gibi, __builtin_expectderleyiciye montaj kodunu nasıl düzenleyeceğiniz konusunda bir ipucu vermek için kullanabilirsiniz . As resmi dokümanlar işaret çoğu durumda, beyninize yerleştirilen montajcı, GCC ekibi tarafından hazırlanmış olan kadar iyi olmayacaktır. Kodunuzu optimize etmek için tahmin etmek yerine gerçek profil verilerini kullanmak her zaman en iyisidir.

Benzer satırlar boyunca, ancak henüz bahsedilmemiştir, derleyiciyi "soğuk" bir yolda kod oluşturmaya zorlamanın GCC'ye özgü bir yoludur. Bu, kulağa tam olarak yaptıkları gibi yapan noinlineve coldözelliklerinin kullanımını içerir . Bu nitelikler yalnızca işlevlere uygulanabilir, ancak C ++ 11 ile satır içi lambda işlevlerini bildirebilirsiniz ve bu iki özellik lambda işlevlerine de uygulanabilir.

Her ne kadar bu, mikro optimizasyonun genel kategorisine girse de ve bu nedenle standart tavsiye geçerlidir - test tahmin etmeyin - daha genel olarak yararlı olduğunu düşünüyorum __builtin_expect. X86 işlemcisinin neredeyse hiçbir nesli, dal tahmin ipuçlarını ( referans ) kullanmaz, bu nedenle her durumda etkileyebileceğiniz tek şey, montaj kodunun sırasıdır. Hata işlemenin veya "uç durum" kodunun ne olduğunu bildiğinizden, bu açıklamayı derleyicinin ona bir dalı asla tahmin etmemesini ve boyut için optimize ederken onu "etkin" koddan uzaklaştırmasını sağlamak için kullanabilirsiniz.

Örnek kullanım:

void FooTheBar(void* pFoo)
{
    if (pFoo == nullptr)
    {
        // Oh no! A null pointer is an error, but maybe this is a public-facing
        // function, so we have to be prepared for anything. Yet, we don't want
        // the error-handling code to fill up the instruction cache, so we will
        // force it out-of-line and onto a "cold" path.
        [&]() __attribute__((noinline,cold)) {
            HandleError(...);
        }();
    }

    // Do normal stuff
    
}

Daha da iyisi, GCC, mevcut olduğunda profil geri bildirimi lehine bunu otomatik olarak yok sayacaktır (örneğin, ile derleme yaparken -fprofile-use).

Resmi belgelere buradan bakın: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes


2
Şube tahmin ipucu önekleri, gerekli olmadıkları için göz ardı edilir; sadece kodunuzu yeniden sıralayarak aynı etkiyi elde edebilirsiniz. (Varsayılan dal tahmin algoritması, geriye doğru dalların alındığını ve ileri dalların alınmadığını tahmin etmektir.) Yani, aslında, CPU'ya bir ipucu verebilirsiniz ve bu da budur __builtin_expect. Hiç de yararsız değil. coldÖzniteliğin de yararlı olduğu konusunda haklısınız , ancak __builtin_expectbence faydasını küçümsüyorsunuz .
Nemo

Modern Intel CPU'lar statik dal tahmini kullanmaz. Tanımladığınız algoritma, @Nemo, burada geriye doğru dalların alındığı ve ileri dalların alınmadığı tahmin edildiği ve daha önceki işlemcilerde ve Pentium M'de kullanıldığı tahmin ediliyor, ancak modern tasarımlar basitçe rastgele tahmin ediyor, dallarına indeksleniyor. o dal hakkında bilgi bulmayı beklediği ve orada bulunan bilgileri kullanan (esasen çöp olsa bile) tablolar . Bu nedenle, dal tahmini ipuçları teorik olarak yararlı olabilir, ancak belki pratikte olmayabilir, bu yüzden Intel bunları kaldırmıştır.
Cody Grey

Açık olmak gerekirse, dal tahmininin uygulanması son derece karmaşıktır ve yorumlardaki alan kısıtlamaları beni fazlasıyla basitleştirmeye zorladı. Bu gerçekten kendi başına tam bir cevap olacaktır. Haswell gibi modern mikro mimarilerde hala statik dal tahminlerinin izleri olabilir, ancak bu eskisi kadar basit değil.
Cody Grey

"Modern Intel CPU'ları statik dal tahmini kullanmaz" için bir referansınız var mı? Intel'in kendi makalesi ( software.intel.com/en-us/articles/… ) aksini söylüyor ... Ama bu 2011'den
Nemo

Gerçekten resmi bir referansınız yok, @Nemo. Intel, çiplerinde kullanılan şube tahmin algoritmaları konusunda son derece sıkıdır ve bunları ticari sır olarak ele alır. Bilinenlerin çoğu deneysel testlerle çözüldü. Her zamanki gibi, Agner Fog'un malzemeleri en iyi kaynaklardır, ancak o bile şöyle diyor: "Şube öngörücüsü Haswell'de yeniden tasarlanmış gibi görünüyor, ancak yapımı hakkında çok az şey biliniyor." Ne yazık ki, statik BP'nin artık kullanılmadığını gösteren kriterleri ilk nerede gördüğümü hatırlayamıyorum.
Cody Grey

5

__builtin_expect, derleyiciye bir dalın hangi yöne gitmesini beklediğinizi söylemek için kullanılabilir. Bu, kodun nasıl üretildiğini etkileyebilir. Tipik işlemciler kodu sırayla daha hızlı çalıştırır. Yani yazarsan

if (__builtin_expect (x == 0, 0)) ++count;
if (__builtin_expect (y == 0, 0)) ++count;
if (__builtin_expect (z == 0, 0)) ++count;

derleyici aşağıdaki gibi kod üretecektir

if (x == 0) goto if1;
back1: if (y == 0) goto if2;
back2: if (z == 0) goto if3;
back3: ;
...
if1: ++count; goto back1;
if2: ++count; goto back2;
if3: ++count; goto back3;

İpucunuz doğruysa, bu, gerçekte gerçekleştirilmiş herhangi bir dallanma olmadan kodu çalıştıracaktır. Her if ifadesinin koşullu kod etrafında dallanacağı ve üç dal çalıştıracağı normal diziden daha hızlı çalışacaktır.

Daha yeni x86 işlemcileri, alınması beklenen dallar veya alınmaması beklenen dallar için talimatlara sahiptir (bir talimat öneki vardır; ayrıntılardan emin değilim). İşlemcinin bunu kullandığından emin değilim. Çok kullanışlı değil, çünkü dal tahmini bunu halledecek. Bu yüzden şube tahminini gerçekten etkileyebileceğinizi sanmıyorum .


2

OP ile ilgili olarak, hayır, GCC'de işlemciye her zaman şubenin alındığını veya alınmadığını varsaymasını söylemenin bir yolu yoktur. Sahip olduğunuz şey __builtin_expect, başkalarının söylediğini yapar. Ayrıca, işlemciye şubenin alınıp alınmadığını her zaman söylemek istemediğinizi düşünüyorum . Intel mimarisi gibi günümüz işlemcileri oldukça karmaşık kalıpları tanıyabilir ve etkili bir şekilde uyum sağlayabilir.

Ancak, varsayılan olarak bir şubenin alınıp alınmayacağının kontrolünü üstlenmek isteyeceğiniz zamanlar vardır : Bildiğiniz zaman, kodun dallanma istatistikleri açısından "soğuk" olarak adlandırılacağını bildiğiniz zaman.

Somut bir örnek: İstisna yönetimi kodu. Tanım gereği yönetim kodu istisnai olarak gerçekleşecektir, ancak belki de oluştuğunda maksimum performans istenir (mümkün olan en kısa sürede ilgilenilmesi gereken kritik bir hata olabilir), bu nedenle varsayılan tahmini kontrol etmek isteyebilirsiniz.

Başka bir örnek: Girişinizi sınıflandırabilir ve sınıflandırmanızın sonucunu işleyen koda atlayabilirsiniz. Çok sayıda sınıflandırma varsa, işlemci istatistikleri toplayabilir ancak bunları kaybedebilir, çünkü aynı sınıflandırma yeterince erken gerçekleşmez ve tahmin kaynakları yakın zamanda adlandırılan koda ayrılmıştır. Keşke işlemciye bazen "bunu önbelleğe almayın" diyebileceğiniz şekilde "lütfen tahmin kaynaklarını bu koda ayırmayın" diyen bir ilkel olsaydı.

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.