Dlaczego GCC generuje 15-20% szybszy Kod, jeśli optymalizuję rozmiar zamiast prędkości?

Po raz pierwszy zauważyłem w 2009 roku, że GCC (przynajmniej na moich projektach i na moich maszynach) mają tendencję do generowania zauważalnie szybszego kodu, jeśli zoptymalizuję Rozmiar (-Os) zamiast speed (-O2 lub -O3) i od tamtej pory zastanawiam się dlaczego.

Udało mi się stworzyć (dość głupi) kod, który pokazuje to zaskakujące zachowanie i jest wystarczająco mały, aby można go tutaj opublikować.

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

Jeśli skompiluję go za pomocą -Os, wykonanie tego programu zajmuje 0,38 s, a 0.44 s jeśli jest skompilowany z -O2 lub -O3. Czasy te są uzyskiwane konsekwentnie i praktycznie bez szumów (gcc 4.7.2, x86_64 GNU/Linux, Intel Core i5-3320M).

(Update: przeniosłem cały kod assembly do GitHub : sprawili, że post był nadęty i najwyraźniej dodali bardzo mało wartości do pytań, ponieważ flagi fno-align-* mają taki sam efekt.)

Oto wygenerowany zestaw z -Os oraz -O2.

Niestety, mój zrozumienie assembly jest bardzo ograniczone, więc nie mam pojęcia, czy to, co zrobiłem dalej, było poprawne: chwyciłem assembly za -O2 i połączyłem wszystkie jego różnice w assembly za -Os Z wyjątkiem linii .p2align, wynik tutaj . Ten kod nadal działa w 0.38 s i jedyna różnica to .p2align rzeczy.

Jeśli dobrze zgaduję, są to podkładki do wyrównania stosu. Zgodnie z dlaczego GCC Pad działa z NOPs? jest robione w nadziei, że kod będzie działać szybciej, ale najwyraźniej ta optymalizacja odwróciła się w moim przypadku.

Czy to wyściółka jest winowajcą w tym przypadku? Dlaczego i jak?

Hałas, który wytwarza, praktycznie uniemożliwia mikro-optymalizację czasu.

Jak mogę się upewnić, że takie przypadkowe wyrównania szczęścia / pecha nie przeszkadzają, gdy robię mikro-optymalizacje (niezwiązane z wyrównaniem stosu) w C lub C++ source kod?


UPDATE:

Podążając za odpowiedź Pascala Cuoqa trochę majstrowałem z wyrównaniami. Po przejściu -O2 -fno-align-functions -fno-align-loops do GCC, wszystkie .p2align znikają z zestawu, a wygenerowany plik wykonywalny działa w ciągu 0,38 s. zgodnie z dokumentacja gcc :

- Os włącza wszystkie optymalizacje - O2 [ale] - Os wyłącza następujące flagi optymalizacji:

  -falign-functions  -falign-jumps  -falign-loops <br/>
  -falign-labels  -freorder-blocks  -freorder-blocks-and-partition <br/>
  -fprefetch-loop-arrays <br/>

Wygląda to na (mis)wyrównanie problem.

Nadal jestem sceptyczny -march=native, jak sugerowano w odpowiedzi Marat Duhan . Nie jestem przekonany, że nie chodzi tylko o ingerencję w ten (błędny)problem wyrównania; nie ma to absolutnie żadnego wpływu na moją maszynę. (Niemniej jednak, podniosłem jego odpowiedź.)


Aktualizacja 2:

Możemy zdjąć -Os ze zdjęcia. następujące czasy są uzyskiwane przez kompilację z

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

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

  • -S -O2 następnie ręcznie przesuwając zespół add() po work() 0.37 s

  • -O2 0.44 s

Wygląda na to, że odległość od miejsca połączenia ma duże znaczenie. Próbowałem perf, ale wyjście perf stat i perf report nie ma dla mnie większego sensu. Jednak mogłem uzyskać tylko jeden spójny wynik:

-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

Dla 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
       ¦    }

Dla -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
Wygląda na to, że opóźniamy połączenie do add()w powolnym przypadku.

Zbadałem Wszystko co perf -e może wypluć na moją maszynę; nie tylko statystyki, które są podane powyżej.

Dla tego samego pliku wykonywalnego stalled-cycles-frontend pokazuje liniową korelację z czasem wykonania; nie zauważyłem niczego innego, co by tak wyraźnie korelowało. (Porównywanie stalled-cycles-frontend dla różnych plików wykonywalnych nie ma sensu ja.)

Dodałem błędy pamięci podręcznej, gdy pojawił się jako pierwszy komentarz. Zbadałem wszystkie błędy pamięci podręcznej, które można zmierzyć na mojej maszynie przez perf, nie tylko te podane powyżej. Chybienia pamięci podręcznej są bardzo głośne i wykazują niewielką lub zerową korelację z czasem wykonania.

Author: Lightness Races in Orbit, 2013-10-20

6 answers

Domyślnie Kompilatory optymalizują dla" przeciętnego " procesora. Ponieważ różne procesory preferują różne sekwencje instrukcji, optymalizacje kompilatorów włączone przez -O2 mogą przynieść korzyści przeciętnemu procesorowi, ale zmniejszyć wydajność konkretnego procesora(i to samo dotyczy -Os). Jeśli spróbujesz tego samego przykładu na różnych procesorach, przekonasz się, że niektóre z nich korzystają z -O2, podczas gdy inne są bardziej korzystne dla -Os optymalizacji.

Oto wyniki dla time ./test 0 0 na kilka procesorów (podany czas użytkownika):

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

W niektórych przypadkach możesz złagodzić efekt niekorzystnych optymalizacji, prosząc gcc o optymalizację dla konkretnego procesora (używając opcji -mtune=native lub -march=native):

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

Aktualizacja: Na Core i3 bazującym na Ivy Bridge trzy wersje gcc (4.6.4, 4.7.3, i 4.8.1) wytwarzają pliki binarne o znacznie różnej wydajności, ale kod złożenia ma tylko subtelne różnice. Jak na razie nie mam wyjaśnienia tego fakt.

Assembly from gcc-4.6.4 -Os (executes in 0.709 secs):

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

Assembly from gcc-4.7.3 -Os (executes in 0.822 secs):

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

Assembly from gcc-4.8.1 -Os (executes in 0.994 secs):

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
 423
Author: Marat Dukhan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-07 21:46:18

Mój kolega pomógł mi znaleźć wiarygodną odpowiedź na moje pytanie. Zauważył znaczenie granicy 256 bajtów. Nie jest zarejestrowany tutaj i zachęcił mnie do napisania odpowiedzi sam (i wziąć całą sławę).


Krótka odpowiedź:

Czy to wyściółka jest winowajcą w tym przypadku? Dlaczego i jak?

Wszystko sprowadza się do wyrównania. wyrównania mogą mieć znaczący wpływ na wydajność, dlatego mamy -falign-* flagi na pierwszym miejscu.

Złożyłem a (fałszywy?) Zgłoś błąd deweloperom gcc . Okazuje się, że domyślnym zachowaniem jest " domyślnie wyrównujemy pętle do 8 bajtów, ale staramy się wyrównać je do 16 bajtów, jeśli nie musimy wypełniać ponad 10 bajtów." najwyraźniej ta wartość domyślna nie jest najlepszym wyborem w tym konkretnym przypadku i na moim komputerze. Clang 3.4 (trunk) z -O3 robi odpowiednie wyrównanie i wygenerowany kod nie pokazuje tego dziwnego zachowanie.

Oczywiście, jeśli niewłaściwe dopasowanie zostanie zrobione, to pogorszy sprawę. niepotrzebne / złe wyrównanie po prostu pochłania bajty bez powodu i potencjalnie zwiększa liczbę braków pamięci podręcznej itp.

Hałas, który wytwarza, sprawia, że mikro-optymalizacje czasowe niemożliwe.

Jak mogę się upewnić, że tak przypadkowe szczęście / pecha nie przeszkadzają mi mikro-optymalizacje (niezwiązane ze stosem alignment) w C lub c++ kody źródłowe?

Po Prostu powiedz gcc, aby wykonała właściwe wyrównanie:

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


Długa odpowiedź:

Kod będzie działał wolniej, jeśli:

  • Granica XX bajtów przecina add() w środku (XX jest zależna od maszyny).

  • Jeśli wywołanie {[9] } musi przeskoczyć granicę XX bajtów, a cel nie jest wyrównany.

  • If add() is not / align = "left" /

  • Jeśli pętla nie jest wyrównana.

Pierwsze 2 są pięknie widoczne na kodach i wynikach, które Marat Dukhan uprzejmie opublikował . W tym przypadku, gcc-4.8.1 -Os (wykonuje w 0.994 sekund):

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

Granica 256 bajtów przecina add() w samym środku i ani add(), ani pętla nie są wyrównane. Niespodzianka, niespodzianka, to najwolniejsza sprawa!

W przypadku gcc-4.7.3 -Os (wykonuje się w 0.822 sek.), granica 256 bajtów przecina się tylko na zimno sekcja (ale ani pętla, ani add() nie jest przecięta):

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>

Nic nie jest wyrównane, a wywołanie add() musi przeskoczyć granicę 256 bajtów. Ten kod jest drugim najwolniejszym.

W przypadku, gdy gcc-4.6.4 -Os (wykonuje się w 0.709 sek.), mimo że nic nie jest wyrównane, wywołanie add() nie musi przeskakiwać przez granicę 256 bajtów, a cel jest dokładnie 32 bajty od siebie:
  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>

To najszybszy ze wszystkich trzech. Dlaczego granica 256 bajtów jest speacial na jego maszynie, zostawię go od niego zależy, czy to rozgryzie. Nie mam takiego procesora.

Teraz, na mojej maszynie nie dostaję tego 256-bajtowego efektu granicznego. Tylko funkcja i wyrównanie pętli działa na mojej maszynie. Jeśli zdam g++ -O2 -falign-functions=16 -falign-loops=16, wszystko wróci do normy: zawsze dostaję najszybszą sprawę, a czas nie jest już wrażliwy na flagę -fno-omit-frame-pointer. Mogę przekazać g++ -O2 -falign-functions=32 -falign-loops=32 lub dowolną wielokrotność 16, Kod też nie jest wrażliwy na to.

Po raz pierwszy zauważyłem w 2009 roku, że gcc (przynajmniej na moim projekty i na mojej maszyn) mają tendencję do generowania zauważalnie szybszego kodu, jeśli I optimize for size (- Os) zamiast speed (- O2 lub-O3) and I have been zastanawiam się, dlaczego.

Prawdopodobnym wyjaśnieniem jest to, że miałem hotspoty, które były wrażliwe na wyrównanie, tak jak w tym przykładzie. Przez mieszanie się z flagami (przechodząc -Os zamiast -O2), hotspoty zostały wyrównane w szczęśliwy sposób przez przypadek i Kod stał się szybszy. to nie miało nic wspólnego optymalizacja pod kątem rozmiaru: dzięki temu hotspoty zostały lepiej wyrównane przez przypadek. od teraz będę sprawdzał efekty dostosowania do moich projektów.

I jeszcze jedno. Jak mogą powstawać takie hotspoty, jak ten pokazany w przykładzie? W jaki sposób inlining tak małej funkcji jak add() może zawieść?

Rozważ to:

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

I w osobnym pliku:

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

I skompilowane jako: g++ -O2 add.cpp main.cpp.

Gcc nie będzie w linii add()!

To wszystko, tak łatwo jest bezinteresownie tworzyć hotspoty, jak ten w OP. oczywiście to częściowo moja wina: gcc jest doskonałym kompilatorem. jeśli skompiluję powyższe jako: g++ -O2 -flto add.cpp main.cpp, czyli jeśli wykonam optymalizację czasu łącza, kod uruchomi się w 0.19 s!

(Inlining jest sztucznie wyłączany w OP, stąd kod w OP był 2x wolniejszy).

 153
Author: Ali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:02

Dodaję ten post-Akceptuj, aby podkreślić, że badano wpływ wyrównania na ogólną wydajność programów - w tym dużych -. Na przykład Ten artykuł (i wierzę, że wersja ta pojawiła się również w CACM) pokazuje, w jaki sposób kolejność połączeń i zmiany rozmiaru środowiska OS były wystarczające, aby znacząco zmienić wydajność. Przypisują to wyrównaniu "gorących pętli".

Ten artykuł, zatytułowany " wytwarzanie błędnych danych bez robienia czegokolwiek oczywiście źle!"mówi, że niezamierzone eksperymentalne uprzedzenia spowodowane niemal niekontrolowanymi różnicami w środowiskach uruchamiania programów prawdopodobnie pozbawiają wiele wyników testów odniesienia znaczenia.

Myślę, że napotkasz inny kąt przy tej samej obserwacji.

W przypadku kodu krytycznego dla wydajności jest to całkiem dobry argument dla Systemów, które oceniają środowisko w czasie instalacji lub uruchomienia i wybierają najlepsze lokalne spośród różnie zoptymalizowanych wersji kluczowych procedur.

 60
Author: Gene,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-27 21:07:00

Myślę, że możesz uzyskać taki sam wynik jak to, co zrobiłeś:

Chwyciłem zespół dla-O2 i połączyłem wszystkie jego różnice w zespole dla-Os z wyjątkiem .linie P2:

... za pomocą -O2 -falign-functions=1 -falign-jumps=1 -falign-loops=1 -falign-labels=1. Kompilowałem wszystko z tymi opcjami, które były szybsze niż zwykłe -O2 za każdym razem, gdy trudziłem się mierzeniem, przez 15 lat.

Również, dla zupełnie innego kontekstu (w tym innego kompilatora), zauważyłem, że sytuacja jest similar : opcja, która ma "optymalizować rozmiar kodu, a nie szybkość", optymalizuje rozmiar i szybkość kodu.

Jeśli dobrze zgaduję, są to podkładki do wyrównania stosu.

Nie, to nie ma nic wspólnego ze stosem, NOPs, które są generowane domyślnie i że opcje-falign - * = 1 prevent służą do wyrównania kodu.

Zgodnie z Dlaczego GCC Pad działa z NOPs? robi się to w nadziei, że kod będzie działał szybciej, ale najwyraźniej w moim przypadku ta optymalizacja przyniosła odwrotny skutek.

Czy to padding jest winowajcą w tym przypadku? Dlaczego i jak?

Jest bardzo prawdopodobne, że padding jest winowajcą. Powodem, dla którego padding jest uważany za konieczny i przydatny w niektórych przypadkach, jest to, że kod jest zwykle pobierany w liniach po 16 bajtów (szczegóły można znaleźć w zasobach optymalizacyjnych Agnera Foga , które różnią się w zależności od modelu procesora). Wyrównanie funkcji, pętli lub etykiety na granicy 16 bajtów oznacza, że statystycznie zwiększa się prawdopodobieństwo, że jedna linia mniej będzie potrzebna do przechowywania funkcji lub pętli. Oczywiście, to działa wstecz, ponieważ te NOP zmniejszają gęstość kodu, a tym samym wydajność pamięci podręcznej. W przypadku pętli i etykiety, NOPs może być nawet konieczne wykonanie raz (gdy wykonanie dociera do pętli/etykiety normalnie, w przeciwieństwie do skoku).

 30
Author: Pascal Cuoq,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-19 21:59:11

Jeśli twój program jest ograniczony kodem L1 cache, optymalizacja pod kątem rozmiaru nagle zaczyna się opłacać.

Kiedy ostatnio sprawdzałem, kompilator nie jest wystarczająco mądry, aby to rozgryźć we wszystkich przypadkach.

W Twoim przypadku,- O3 prawdopodobnie generuje kod wystarczający dla dwóch linii bufora, ale-Os mieści się w jednej linii bufora.

 11
Author: Joshua,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-24 15:48:50

Bynajmniej nie jestem ekspertem w tej dziedzinie, ale pamiętam, że nowoczesne procesory są dość wrażliwe, jeśli chodzi o przewidywanie gałęzi . Algorytmy używane do przewidywania gałęzi są (a przynajmniej były w czasach, gdy pisałem kod asemblera) oparte na kilku właściwościach kodu, w tym odległości celu i kierunku.

Scenariusz, który przychodzi na myśl, to małe pętle. Gdy gałąź cofała się i odległość nie była zbyt duża, przewidywanie gałęzi optymalizowało się w tym przypadku, ponieważ wszystkie małe pętle są wykonane w ten sposób. Te same zasady mogą wejść w grę, gdy zamienisz lokalizację add i work w wygenerowanym kodzie lub gdy pozycja obu nieznacznie się zmieni.

To powiedziawszy, nie mam pojęcia, jak to zweryfikować i chciałem tylko dać Ci znać, że może to być coś, co chcesz sprawdzić.
 5
Author: Daniel Frey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:09