Hız yerine boyut için optimize edersem neden GCC% 15-20 daha hızlı kod üretir?


445

İlk olarak 2009'da GCC'nin (en azından projelerimde ve makinelerimde) hız ( veya ) yerine size ( -Os) için optimize edersem fark edilir derecede daha hızlı kod üretme eğiliminde olduğunu fark ettim ve nedenini merak ediyorum.-O2-O3

Bu şaşırtıcı davranışı gösteren ve burada gönderilecek kadar küçük bir kod (oldukça aptal) oluşturmayı başardık.

const int LOOP_BOUND = 200000000;

__attribute__((noinline))
static int add(const int& x, const int& y) {
    return x + y;
}

__attribute__((noinline))
static int work(int xval, int yval) {
    int sum(0);
    for (int i=0; i<LOOP_BOUND; ++i) {
        int x(xval+sum);
        int y(yval+sum);
        int z = add(x, y);
        sum += z;
    }
    return sum;
}

int main(int , char* argv[]) {
    int result = work(*argv[1], *argv[2]);
    return result;
}

Derlediğimde -Os, bu programı yürütmek için 0.38 s -O2veya derlenmişse 0.44 s sürer -O3. Bu süreler tutarlı bir şekilde ve neredeyse hiç gürültü olmadan elde edilir (gcc 4.7.2, x86_64 GNU / Linux, Intel Core i5-3320M).

(Güncelleme: Tüm montaj kodunu GitHub'a taşıdım : Gönderiyi şişirdiler ve fno-align-*bayraklar aynı etkiye sahip olduğu için sorulara çok az değer kattılar.)

Burada ile montaj oluşturulur -Osve -O2.

Ne yazık ki, montaj benim anlayış çok hiç bir fikrim yok bu yüzden, sınırlı olmadığını önümüzdeki yaptığı doğru ne olduğunu: Ben montaj yakaladı -O2ve montaj içine tüm farklılıkları birleşti -Os hariç.p2align hatları, yol burada . Bu kod hala 0.38s'de çalışır ve tek fark şeydir .p2align .

Doğru tahmin edersem, bunlar yığın hizalaması için dolgulardır. Göre NOPs ile GCC ped fonksiyonlarını yapar Neden? kodun daha hızlı çalışması umuduyla yapılır, ancak görünüşe göre bu optimizasyon benim durumumda geri tepti.

Bu durumda suçlu olan dolgu var mı? Neden ve nasıl?

Yaptığı gürültü, zamanlama mikro optimizasyonlarını imkansız hale getiriyor.

C veya C ++ kaynak kodunda mikro optimizasyonlar (yığın hizalamasıyla ilgisiz) yaptığımda bu tür kazara şanslı / şanssız hizalamaların parazit yapmadığından nasıl emin olabilirim?


GÜNCELLEME:

Pascal Cuoq'un cevabını takiben hizalamalarla biraz uğraştım . Gcc'ye geçerek -O2 -fno-align-functions -fno-align-loops, hepsi .p2alignderlemeden gider ve oluşturulan yürütülebilir dosya 0,38 saniyede çalışır. Göre gcc belgelerinde :

-Os tüm -O2 optimizasyonlarını etkinleştirir [ama] -Os aşağıdaki optimizasyon bayraklarını devre dışı bırakır:

  -falign-functions  -falign-jumps  -falign-loops
  -falign-labels  -freorder-blocks  -freorder-blocks-and-partition
  -fprefetch-loop-arrays

Yani, hemen hemen (yanlış) bir hizalama sorunu gibi görünüyor.

Hala Marat Dükhan'ın cevabında-march=native önerildiği gibi şüpheliyim . Sadece bu (yanlış) hizalama sorununa müdahale etmediğine ikna olmadım; makinem üzerinde kesinlikle bir etkisi yok. (Yine de cevabını iptal ettim.)


GÜNCELLEME 2:

-OsResmi çıkarabiliriz . Aşağıdaki süreler derlenerek elde edilir.

  • -O2 -fno-omit-frame-pointer 0.37s

  • -O2 -fno-align-functions -fno-align-loops 0.37s

  • -S -O2add()sonra work()0.37s sonra montaj el ile hareket

  • -O2 0.44s

Bana öyle add()geliyor ki çağrı sitesinden uzaklığı çok önemli. Denedim perf, ama çıktısı perf statve perf reportbana çok az mantıklı. Ancak, bundan sadece bir tutarlı sonuç alabilirim:

-O2:

 602,312,864 stalled-cycles-frontend   #    0.00% frontend cycles idle
       3,318 cache-misses
 0.432703993 seconds time elapsed
 [...]
 81.23%  a.out  a.out              [.] work(int, int)
 18.50%  a.out  a.out              [.] add(int const&, int const&) [clone .isra.0]
 [...]
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
       ¦       return x + y;
100.00 ¦     lea    (%rdi,%rsi,1),%eax
       ¦   }
       ¦   ? retq
[...]
       ¦            int z = add(x, y);
  1.93 ¦    ? callq  add(int const&, int const&) [clone .isra.0]
       ¦            sum += z;
 79.79 ¦      add    %eax,%ebx

Şunun için fno-align-*:

 604,072,552 stalled-cycles-frontend   #    0.00% frontend cycles idle
       9,508 cache-misses
 0.375681928 seconds time elapsed
 [...]
 82.58%  a.out  a.out              [.] work(int, int)
 16.83%  a.out  a.out              [.] add(int const&, int const&) [clone .isra.0]
 [...]
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
       ¦       return x + y;
 51.59 ¦     lea    (%rdi,%rsi,1),%eax
       ¦   }
[...]
       ¦    __attribute__((noinline))
       ¦    static int work(int xval, int yval) {
       ¦        int sum(0);
       ¦        for (int i=0; i<LOOP_BOUND; ++i) {
       ¦            int x(xval+sum);
  8.20 ¦      lea    0x0(%r13,%rbx,1),%edi
       ¦            int y(yval+sum);
       ¦            int z = add(x, y);
 35.34 ¦    ? callq  add(int const&, int const&) [clone .isra.0]
       ¦            sum += z;
 39.48 ¦      add    %eax,%ebx
       ¦    }

Şunun için -fno-omit-frame-pointer:

 404,625,639 stalled-cycles-frontend   #    0.00% frontend cycles idle
      10,514 cache-misses
 0.375445137 seconds time elapsed
 [...]
 75.35%  a.out  a.out              [.] add(int const&, int const&) [clone .isra.0]                                                                                     ¦
 24.46%  a.out  a.out              [.] work(int, int)
 [...]
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
 18.67 ¦     push   %rbp
       ¦       return x + y;
 18.49 ¦     lea    (%rdi,%rsi,1),%eax
       ¦   const int LOOP_BOUND = 200000000;
       ¦
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
       ¦     mov    %rsp,%rbp
       ¦       return x + y;
       ¦   }
 12.71 ¦     pop    %rbp
       ¦   ? retq
 [...]
       ¦            int z = add(x, y);
       ¦    ? callq  add(int const&, int const&) [clone .isra.0]
       ¦            sum += z;
 29.83 ¦      add    %eax,%ebx

Görünüşe göre add()yavaş durumda çağrıda duruyoruz .

Ben inceledim her şeyiperf -e benim makinede tükürür olabilir; sadece yukarıda verilen istatistikler değil.

Aynı yürütülebilir dosya stalled-cycles-frontendiçin yürütme süresi ile doğrusal korelasyon gösterir; Bu kadar açık bir şekilde ilişkilendirilecek başka bir şey fark etmedim. ( stalled-cycles-frontendFarklı yürütülebilir dosyaları karşılaştırmak benim için anlamlı değil.)

İlk yorum olarak geldi önbellek özledim dahil. perfSadece yukarıda verilenlerle değil , makinemde ölçülebilen tüm önbellek hatalarını inceledim . Önbellek özlüyor çok gürültülü ve yürütme süreleri ile çok az korelasyon gösterir.


36
Kör tahmin: Bu bir önbellek özledim olabilir mi?

@ H2CO3 Bu da benim ilk düşüncemdi, ama OP'nin sorusunu derinlemesine okumadan ve anlamadan yorumu yayınlamak için yeterince teşvik edilmedi.
ῥεῖντα ῥεῖ

2
@ g-makulik Bu yüzden bir "kör tahmin" ;-) "TL; DR" kötü sorular için ayrıldığını uyardım. : P

3
Sadece ilginç bir veri noktası: Bunu OS X'deki clang ile derlediğimde -O3 veya -Ofast'ın -Os kadar 1.5x kadar hızlı olduğunu görüyorum. (Gcc ile çoğaltmayı denemedim.)
Rob Napier

2
Aynı kod. .L3'ün adresine daha yakından bakın, yanlış hizalanmış şube hedefleri pahalıdır.
Hans Passant

Yanıtlar:


505

Varsayılan olarak derleyiciler "ortalama" işlemci için optimize eder. Farklı işlemciler farklı komut dizilerini desteklediğinden, derleyici optimizasyonları tarafından etkinleştirilen -O2ortalama işlemciye fayda sağlayabilir, ancak işlemcinizdeki performansı düşürebilir (ve aynısı geçerlidir -Os). Aynı örneği farklı işlemcilerde denerseniz, bazılarında yararlanırken, -O2diğerlerinin -Osoptimizasyonlara daha elverişli olduğunu göreceksiniz .

time ./test 0 0Birkaç işlemcinin sonuçları (rapor edilen kullanıcı süresi):

Processor (System-on-Chip)             Compiler   Time (-O2)  Time (-Os)  Fastest
AMD Opteron 8350                       gcc-4.8.1    0.704s      0.896s      -O2
AMD FX-6300                            gcc-4.8.1    0.392s      0.340s      -Os
AMD E2-1800                            gcc-4.7.2    0.740s      0.832s      -O2
Intel Xeon E5405                       gcc-4.8.1    0.603s      0.804s      -O2
Intel Xeon E5-2603                     gcc-4.4.7    1.121s      1.122s       -
Intel Core i3-3217U                    gcc-4.6.4    0.709s      0.709s       -
Intel Core i3-3217U                    gcc-4.7.3    0.708s      0.822s      -O2
Intel Core i3-3217U                    gcc-4.8.1    0.708s      0.944s      -O2
Intel Core i7-4770K                    gcc-4.8.1    0.296s      0.288s      -Os
Intel Atom 330                         gcc-4.8.1    2.003s      2.007s      -O2
ARM 1176JZF-S (Broadcom BCM2835)       gcc-4.6.3    3.470s      3.480s      -O2
ARM Cortex-A8 (TI OMAP DM3730)         gcc-4.6.3    2.727s      2.727s       -
ARM Cortex-A9 (TI OMAP 4460)           gcc-4.6.3    1.648s      1.648s       -
ARM Cortex-A9 (Samsung Exynos 4412)    gcc-4.6.3    1.250s      1.250s       -
ARM Cortex-A15 (Samsung Exynos 5250)   gcc-4.7.2    0.700s      0.700s       -
Qualcomm Snapdragon APQ8060A           gcc-4.8       1.53s       1.52s      -Os

Bazı durumlarda dezavantajlı optimizasyonların etkisini, gccözel işlemciniz için optimize etmeyi (seçenekleri -mtune=nativeveya -march=native) kullanarak hafifletebilirsiniz :

Processor            Compiler   Time (-O2 -mtune=native) Time (-Os -mtune=native)
AMD FX-6300          gcc-4.8.1         0.340s                   0.340s
AMD E2-1800          gcc-4.7.2         0.740s                   0.832s
Intel Xeon E5405     gcc-4.8.1         0.603s                   0.803s
Intel Core i7-4770K  gcc-4.8.1         0.296s                   0.288s

Güncelleme: Ivy Bridge tabanlı Core üç versiyonu i3 gcc( 4.6.4, 4.7.3ve 4.8.1önemli ölçüde farklı performansla) üreten ikili, ancak montaj kodu yalnızca ince varyasyonları vardır. Şimdiye kadar, bu gerçeğin hiçbir açıklaması yok.

Meclis gcc-4.6.4 -Os(0.709 saniyede çalışır):

00000000004004d2 <_ZL3addRKiS0_.isra.0>:
  4004d2:       8d 04 37                lea    eax,[rdi+rsi*1]
  4004d5:       c3                      ret

00000000004004d6 <_ZL4workii>:
  4004d6:       41 55                   push   r13
  4004d8:       41 89 fd                mov    r13d,edi
  4004db:       41 54                   push   r12
  4004dd:       41 89 f4                mov    r12d,esi
  4004e0:       55                      push   rbp
  4004e1:       bd 00 c2 eb 0b          mov    ebp,0xbebc200
  4004e6:       53                      push   rbx
  4004e7:       31 db                   xor    ebx,ebx
  4004e9:       41 8d 34 1c             lea    esi,[r12+rbx*1]
  4004ed:       41 8d 7c 1d 00          lea    edi,[r13+rbx*1+0x0]
  4004f2:       e8 db ff ff ff          call   4004d2 <_ZL3addRKiS0_.isra.0>
  4004f7:       01 c3                   add    ebx,eax
  4004f9:       ff cd                   dec    ebp
  4004fb:       75 ec                   jne    4004e9 <_ZL4workii+0x13>
  4004fd:       89 d8                   mov    eax,ebx
  4004ff:       5b                      pop    rbx
  400500:       5d                      pop    rbp
  400501:       41 5c                   pop    r12
  400503:       41 5d                   pop    r13
  400505:       c3                      ret

Montaj: gcc-4.7.3 -Os(0,822 saniye içinde çalışır):

00000000004004fa <_ZL3addRKiS0_.isra.0>:
  4004fa:       8d 04 37                lea    eax,[rdi+rsi*1]
  4004fd:       c3                      ret

00000000004004fe <_ZL4workii>:
  4004fe:       41 55                   push   r13
  400500:       41 89 f5                mov    r13d,esi
  400503:       41 54                   push   r12
  400505:       41 89 fc                mov    r12d,edi
  400508:       55                      push   rbp
  400509:       bd 00 c2 eb 0b          mov    ebp,0xbebc200
  40050e:       53                      push   rbx
  40050f:       31 db                   xor    ebx,ebx
  400511:       41 8d 74 1d 00          lea    esi,[r13+rbx*1+0x0]
  400516:       41 8d 3c 1c             lea    edi,[r12+rbx*1]
  40051a:       e8 db ff ff ff          call   4004fa <_ZL3addRKiS0_.isra.0>
  40051f:       01 c3                   add    ebx,eax
  400521:       ff cd                   dec    ebp
  400523:       75 ec                   jne    400511 <_ZL4workii+0x13>
  400525:       89 d8                   mov    eax,ebx
  400527:       5b                      pop    rbx
  400528:       5d                      pop    rbp
  400529:       41 5c                   pop    r12
  40052b:       41 5d                   pop    r13
  40052d:       c3                      ret

Montaj yeri gcc-4.8.1 -Os(0,994 saniye içinde çalışır):

00000000004004fd <_ZL3addRKiS0_.isra.0>:
  4004fd:       8d 04 37                lea    eax,[rdi+rsi*1]
  400500:       c3                      ret

0000000000400501 <_ZL4workii>:
  400501:       41 55                   push   r13
  400503:       41 89 f5                mov    r13d,esi
  400506:       41 54                   push   r12
  400508:       41 89 fc                mov    r12d,edi
  40050b:       55                      push   rbp
  40050c:       bd 00 c2 eb 0b          mov    ebp,0xbebc200
  400511:       53                      push   rbx
  400512:       31 db                   xor    ebx,ebx
  400514:       41 8d 74 1d 00          lea    esi,[r13+rbx*1+0x0]
  400519:       41 8d 3c 1c             lea    edi,[r12+rbx*1]
  40051d:       e8 db ff ff ff          call   4004fd <_ZL3addRKiS0_.isra.0>
  400522:       01 c3                   add    ebx,eax
  400524:       ff cd                   dec    ebp
  400526:       75 ec                   jne    400514 <_ZL4workii+0x13>
  400528:       89 d8                   mov    eax,ebx
  40052a:       5b                      pop    rbx
  40052b:       5d                      pop    rbp
  40052c:       41 5c                   pop    r12
  40052e:       41 5d                   pop    r13
  400530:       c3                      ret

186
Sadece açıklığa kavuşturmak için: OP kodunun performansını 12 farklı platformda gerçekten ölçtünüz mü? (Bunu yapacağınızı düşündüğünüz için +1)
anatolyg

194
@anatolyg Evet, yaptım! (ve yakında biraz daha ekleyecek)
Marat Dukhan

43
Aslında. Sadece farklı CPU'lar hakkında teorik olmakla kalmayıp aynı zamanda bunu kanıtlamak için bir +1 . Hızla ilgili her cevapta gördüğünüz bir şey (ne yazık ki) değil. Bu testler aynı işletim sistemiyle mi çalışıyor? (Mümkün olduğunca bu
çarpıklığı gösteriyor

7
@Ali AMD-FX 6300'e -O2 -fno-align-functions -fno-align-loopszaman kazandırır 0.340s, böylece hizalama ile açıklanabilir. Ancak, en uygun hizalama işlemciye bağlıdır: bazı işlemciler hizalanmış döngüler ve işlevleri tercih eder.
Marat Dukhan

13
@Jongware İşletim sisteminin sonuçları nasıl önemli ölçüde etkileyeceğini görmüyorum; döngü asla sistem çağrısı yapmaz.
Ali

186

Meslektaşım soruma makul bir cevap bulmama yardım etti. 256 baytlık sınırın önemini fark etti. Burada kayıtlı değil ve cevabı kendim göndermem için beni teşvik etti (ve tüm şöhreti almaya).


Kısa cevap:

Bu durumda suçlu olan dolgu var mı? Neden ve nasıl?

Her şey hizalanmaya kadar kaynar. Hizalamaların performans üzerinde önemli bir etkisi olabilir, bu yüzden -falign-*ilk etapta bayraklarımız var .

Ben göndermiş bir (Bogus?) Hata raporu gcc geliştiricilerine . Varsayılan davranışın "döngüleri varsayılan olarak 8 bayta hizaladığımız ancak 10 bayttan fazla doldurmamız gerekmiyorsa 16 bayta hizalamaya çalıştığımız" ortaya çıkıyor. Görünüşe göre, bu varsayılan bu özel durumda ve makinemde en iyi seçim değil. Clang 3.4 (trunk) ile -O3uygun hizalamayı yapar ve oluşturulan kod bu garip davranışı göstermez.

Tabii ki, uygun olmayan bir hizalama yapılırsa, işleri daha da kötüleştirir. Gereksiz / kötü bir hizalama hiçbir sebep olmadan bayt tüketir ve potansiyel olarak önbellek hatalarını vb. Artırır.

Yaptığı gürültü, zamanlama mikro optimizasyonlarını imkansız hale getiriyor.

C veya C ++ kaynak kodlarında mikro optimizasyonlar (yığın hizalamasıyla ilgisiz) yaptığımda bu tür kazara şanslı / şanssız hizalamaların parazit yapmadığından nasıl emin olabilirim?

Sadece gcc'ye doğru hizalamayı yapmasını söyleyerek:

g++ -O2 -falign-functions=16 -falign-loops=16


Uzun cevap:

Aşağıdaki durumlarda kod daha yavaş çalışır:

  • bir XXbayt sınırı add()ortadan kesilir ( XXmakineye bağımlıdır).

  • çağrısının add()bir XXbayt sınırı aşması gerekiyorsa ve hedef hizalanmamışsa.

  • Eğer add()uyumlu değildir.

  • döngü hizalanmamışsa.

İlk 2, Marat Dukhan'ın nazikçe yayınladığı kodlar ve sonuçlar üzerinde güzel bir şekilde görülebilir . Bu durumda, gcc-4.8.1 -Os(0,994 saniye içinde çalışır):

00000000004004fd <_ZL3addRKiS0_.isra.0>:
  4004fd:       8d 04 37                lea    eax,[rdi+rsi*1]
  400500:       c3   

256 baytlık bir sınır add(), tam ortada kesilir ve ne add()döngü ne de hizalama yapılır. Sürpriz, sürpriz, bu en yavaş durum!

Durumda gcc-4.7.3 -Os(0.822 saniye içinde yürütülür), 256 bayt sınırı yalnızca soğuk bir bölüme keser (ancak döngü veya add()kesilmez):

00000000004004fa <_ZL3addRKiS0_.isra.0>:
  4004fa:       8d 04 37                lea    eax,[rdi+rsi*1]
  4004fd:       c3                      ret

[...]

  40051a:       e8 db ff ff ff          call   4004fa <_ZL3addRKiS0_.isra.0>

Hiçbir şey hizalanmamış ve çağrının add()256 bayt sınırını aşması gerekiyor. Bu kod en yavaş ikinci koddur.

Durumda gcc-4.6.4 -Oshiçbir şey hizalanmış halde (0.709 saniye içinde yürütür), çağrı için add()256 bayt sınırını üzerinden atlamak zorunda değildir ve hedef tam 32 bayt uzaklıktadır:

  4004f2:       e8 db ff ff ff          call   4004d2 <_ZL3addRKiS0_.isra.0>
  4004f7:       01 c3                   add    ebx,eax
  4004f9:       ff cd                   dec    ebp
  4004fb:       75 ec                   jne    4004e9 <_ZL4workii+0x13>

Bu üçünün de en hızlısı. 256 baytlık sınır neden makinede özelse, onu anlamak için ona bırakacağım. Böyle bir işlemcim yok.

Şimdi, makinemde bu 256 bayt sınır efektini almıyorum. Makinemde yalnızca işlev ve döngü hizalaması devreye giriyor. Eğer geçersem g++ -O2 -falign-functions=16 -falign-loops=16her şey normale döner: Her zaman en hızlı davayı alırım ve zaman -fno-omit-frame-pointerartık bayrağa karşı duyarlı değildir . Ben geçebilir g++ -O2 -falign-functions=32 -falign-loops=32veya 16 katları, kodu da duyarlı değildir.

İlk olarak 2009'da gcc'nin (en azından projelerimde ve makinelerimde) hız (-O2 veya -O3) yerine boyut (-Os) için optimize edersem fark edilir derecede daha hızlı kod üretme eğiliminde olduğunu fark ettim ve merak ediyorum neden beri o zaman.

Muhtemel bir açıklama, bu örnekteki gibi hizalamaya duyarlı olan sıcak noktalara sahip olduğumdur. Bayraklarla uğraşarak ( -Osyerine geçerek -O2), bu sıcak noktalar kazara şanslı bir şekilde hizalandı ve kod daha hızlı hale geldi. Boyut için optimize etmekle ilgisi yoktu: Bunlar, sıcak noktaların daha iyi hizalandığı ciddi bir kazaydı. Şu andan itibaren, uyumun projelerim üzerindeki etkilerini kontrol edeceğim.

Oh, ve bir şey daha. Örnekte gösterilen gibi bu sıcak noktalar nasıl ortaya çıkabilir? Böyle küçük bir işlevin satır içi ayarı nasıl add()başarısız olabilir?

Bunu düşün:

// add.cpp
int add(const int& x, const int& y) {
    return x + y;
}

ve ayrı bir dosyada:

// main.cpp
int add(const int& x, const int& y);

const int LOOP_BOUND = 200000000;

__attribute__((noinline))
static int work(int xval, int yval) {
    int sum(0);
    for (int i=0; i<LOOP_BOUND; ++i) {
        int x(xval+sum);
        int y(yval+sum);
        int z = add(x, y);
        sum += z;
    }
    return sum;
}

int main(int , char* argv[]) {
    int result = work(*argv[1], *argv[2]);
    return result;
}

gibi derlenmiş: g++ -O2 add.cpp main.cpp.

      gcc satır içi olmaz add()!

Hepsi bu, OP'deki gibi sıcak noktalar istemeden oluşturmak çok kolay. Tabii ki kısmen benim hatam: gcc mükemmel bir derleyicidir. Yukarıdaki gibi derlemek: g++ -O2 -flto add.cpp main.cppyani, eğer bağlantı süresi optimizasyonu yaparsanız, kod 0.19s çalışır!

(Inlining, OP'de yapay olarak devre dışı bırakılır, bu nedenle OP'deki kod 2 kat daha yavaştı).


19
Vay canına ... Bu kesinlikle kıyaslama anomalilerini aşmak için yaptığımın ötesine geçiyor.
Gizemli

@Ali Sanırım bu mantıklı çünkü derleyici görmediği bir şeyi nasıl sıralayabilir? Muhtemelen inlinebaşlıkta + işlev tanımını kullanmamızın nedeni budur . Gc'de lto'nun ne kadar olgun olduğundan emin değilim Benim en azından mingw onunla deneyimim bir hit ya da özledim.
greatwolf

7
Birkaç yıl önce oldukça büyük uygulamalar (perl, Spice, vb.) Çalıştırmakla ilgili bir makalesi olan ACM'nin İletişim olduğunu düşünüyorum ve ikili görüntüyü farklı boyutlardaki Linux ortamlarını kullanarak bir seferde bir bayt kaydırıyor. % 15 ya da daha fazla tipik varyansı hatırlıyorum. Onların özeti, bu dış hizalama değişkeni dikkate alınmadığından, birçok kıyaslama sonucunun işe yaramadığıydı.
Gene

1
özellikle -flto. Daha önce hiç kullanmadıysanız, deneyimden
underscore_d

2
Bu, hizalamanın performansı nasıl etkileyebileceği ve nasıl profil oluşturulacağı hakkında konuşan harika bir video: youtube.com/watch?time_continue=1&v=r-TLSBdHe1A
Zhro

73

Bu son kabulü, hizalamanın büyük programlar da dahil olmak üzere programların genel performansı üzerindeki etkilerinin incelendiğini belirtmek için ekliyorum. Örneğin, bu makale (ve bunun bir versiyonunun CACM'de de göründüğüne inanıyorum), bağlantı sırası ve işletim sistemi ortam boyutu değişikliklerinin tek başına performansı önemli ölçüde değiştirmek için nasıl yeterli olduğunu gösterir. Bunu "sıcak döngülerin" hizalanmasına bağlarlar.

"Açıkçası yanlış bir şey yapmadan yanlış veri üretmek!" program çalışma ortamlarındaki neredeyse kontrol edilemeyen farklılıklar nedeniyle deneysel önyargıların muhtemelen birçok karşılaştırma sonucunu anlamsız kıldığını söylüyor.

Aynı gözlemde farklı bir açıyla karşılaştığınızı düşünüyorum.

Performans açısından kritik öneme sahip kodlar için bu, ortamı yükleme veya çalışma zamanında değerlendiren ve anahtar rutinlerinin farklı şekilde optimize edilmiş sürümleri arasında yerel en iyiyi seçen sistemler için oldukça iyi bir argüman.


33

Yaptığınız sonuçla aynı sonucu elde edebileceğinizi düşünüyorum:

Derlemeyi -O2 için yakaladım ve .p2align satırları dışındaki tüm farklılıkları -Os için derlemeye birleştirdim:

… Kullanarak -O2 -falign-functions=1 -falign-jumps=1 -falign-loops=1 -falign-labels=1. Her şeyi -O2ölçmek için rahatsız ettiğim her zamankinden daha hızlı olan bu seçeneklerle 15 yıldır her şeyi derliyorum .

Ayrıca, tamamen farklı bir bağlam için (farklı bir derleyici dahil), durumun benzer olduğunu fark ettim : “hız yerine kod boyutunu optimize etme” seçeneği, kod boyutu ve hızı için optimize eder.

Doğru tahmin edersem, bunlar yığın hizalaması için dolgulardır.

Hayır, bunun yığınla ilgisi yoktur, varsayılan olarak oluşturulan NOP'lar ve -falign - * = 1 engelleme seçenekleri kod hizalaması içindir.

GCC pedi neden NOP'larla çalışıyor? kodun daha hızlı çalışacağını umuyorum, ama görünüşe göre bu optimizasyon benim durumumda geri tepti.

Bu durumda suçlu olan dolgu var mı? Neden ve nasıl?

Dolgunun suçlu olması çok muhtemeldir. Dolgunun gerekli olduğu ve bazı durumlarda yararlı olmasının nedeni, kodun genellikle 16 baytlık satırlarda getirilmesidir (işlemci modeline göre değişen ayrıntılar için Agner Fog'un optimizasyon kaynaklarına bakın). Bir işlev, döngü veya etiketi 16 baytlık bir sınırda hizalamak, işlevi veya döngüyü içermek için daha az satırın gerekli olması olasılığının istatistiksel olarak arttığı anlamına gelir. Açıkçası, bu NOP'lar kod yoğunluğunu ve dolayısıyla önbellek verimliliğini azalttığı için geri teper. Döngüler ve etiket durumunda, NOP'ların bir kez yürütülmesi gerekebilir (yürütme bir atlamadan ziyade normalde döngüye / etikete ulaştığında).


Komik olan şey: -O2 -fno-omit-frame-pointeren az onun kadar iyi -Os. Lütfen güncellenmiş soruyu kontrol edin.
Ali

11

Programınız CODE L1 önbelleği ile sınırlıysa, boyut için optimizasyon aniden ödeme yapmaya başlar.

En son kontrol ettiğimde, derleyici bunu her durumda anlayacak kadar akıllı değil.

Sizin durumunuzda, -O3 muhtemelen iki önbellek satırı için yeterli kod üretir, ancak -Os bir önbellek satırına sığar.


1
Bu align = parametrelerine ne kadar bahis yatırmak istediğiniz önbellek satırlarının boyutu ile ilgilidir?
Joshua

Artık umrumda değil: Makinemde görünmüyor. Ve -falign-*=16bayrakları geçerek , her şey normale döndü, her şey tutarlı bir şekilde davranıyor. Bence bu soru çözüldü.
Ali

7

Hiçbir şekilde bu alanda uzman değilim, ancak modern işlemcilerin şube tahmini söz konusu olduğunda oldukça hassas olduklarını hatırlıyorum . Dalları tahmin etmek için kullanılan algoritmalar, bir hedefin mesafesi ve yönü de dahil olmak üzere, kodun çeşitli özelliklerine dayalı olarak (veya en azından montajcı kodunu yazdığım günlerde geri döndü).

Akla gelen senaryo küçük döngülerdir. Şube geriye doğru giderken ve mesafe çok uzak olmadığında, tüm küçük döngüler bu şekilde yapıldığından, şube tahmini bu durum için optimize edildi. Oluşturulan kodun yerini addve workkodunu değiştirdiğinizde veya her ikisinin de konumu biraz değiştiğinde aynı kurallar geçerli olabilir .

Bununla birlikte, bunu nasıl doğrulayacağım hakkında hiçbir fikrim yok ve sadece bunun bakmak istediğiniz bir şey olabileceğini size bildirmek istedim.


Teşekkürler. Onunla oynanan: Sadece değiştirerek hıza kalkmak add()ve work()eğer -O2geçirilir. Diğer tüm durumlarda kod değiştirilerek önemli ölçüde yavaşlar. Hafta sonu boyunca şube tahmin / yanlış tahmin istatistiklerini de analiz perfettim ve bu garip davranışı açıklayabilecek hiçbir şey fark etmedim. Tek tutarlı sonuç, yavaş durumda perf100.0 inç raporları ve döngüdeki add()çağrının hemen ardından satırda büyük bir değer olmasıdır add(). Görünüşe göre add()yavaş durumda bir sebepten dolayı duruyoruz ama hızlı koşularda değil.
Ali

Intel'in VTune'sini makinelerimden birine kurmayı ve kendimi bir profil oluşturmayı düşünüyorum. perfyalnızca sınırlı sayıda şeyi destekler, belki Intel'in işleri kendi işlemcilerinde biraz daha kullanışlıdır.
Ali
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.