GNU/Linux >> Znalost Linux >  >> Linux

Jaká je výhoda __builtin_expect GCC v prohlášení if else?

Představte si kód sestavení, který by byl vygenerován z:

if (__builtin_expect(x, 0)) {
    foo();
    ...
} else {
    bar();
    ...
}

Myslím, že by to mělo být něco jako:

  cmp   $x, 0
  jne   _foo
_bar:
  call  bar
  ...
  jmp   after_if
_foo:
  call  foo
  ...
after_if:

Můžete vidět, že instrukce jsou uspořádány v takovém pořadí, že bar malá a velká písmena předchází foo pouzdro (na rozdíl od kódu C). To může lépe využít kanál CPU, protože skok zničí již načtené instrukce.

Před provedením skoku se zobrazí instrukce pod ním (bar pouzdro) jsou tlačeny do potrubí. Od foo případ je nepravděpodobný, skákání je také nepravděpodobné, proto je nepravděpodobné rozbití potrubí.


Pojďme provést dekompilaci, abychom viděli, co s tím GCC 4.8 dělá

Blagovest zmínil inverzi větví, aby zlepšil pipeline, ale skutečně to dělají současné kompilátory? Pojďme to zjistit!

Bez __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)
        puts("a");
    return 0;
}

Kompilace a dekompilace pomocí GCC 4.8.2 x86_64 Linux:

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

Výstup:

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 0a                   jne    1a <main+0x1a>
  10:       bf 00 00 00 00          mov    $0x0,%edi
                    11: R_X86_64_32 .rodata.str1.1
  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

Pořadí instrukcí v paměti bylo nezměněno:nejprve puts a poté retq návrat.

S __builtin_expect

Nyní nahraďte if (i) s:

if (__builtin_expect(i, 0))

a dostaneme:

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 07                   je     17 <main+0x17>
  10:       31 c0                   xor    %eax,%eax
  12:       48 83 c4 08             add    $0x8,%rsp
  16:       c3                      retq
  17:       bf 00 00 00 00          mov    $0x0,%edi
                    18: R_X86_64_32 .rodata.str1.1
  1c:       e8 00 00 00 00          callq  21 <main+0x21>
                    1d: R_X86_64_PC32       puts-0x4
  21:       eb ed                   jmp    10 <main+0x10>

puts byl přesunut na úplný konec funkce, retq vraťte se!

Nový kód je v podstatě stejný jako:

int i = !time(NULL);
if (i)
    goto puts;
ret:
return 0;
puts:
puts("a");
goto ret;

Tato optimalizace nebyla provedena pomocí -O0 .

Ale hodně štěstí při psaní příkladu, který běží rychleji s __builtin_expect než bez, CPU jsou v té době opravdu chytré. Moje naivní pokusy jsou tady.

C++20 [[likely]] a [[unlikely]]

C++20 standardizovalo tyto vestavěné prvky C++:Jak použít atribut pravděpodobné/nepravděpodobné C++20 v příkazu if-else Pravděpodobně (slovní hříčka!) udělají totéž.


Myšlenka __builtin_expect je sdělit kompilátoru, že obvykle zjistíte, že výraz je vyhodnocen jako c, takže kompilátor může pro tento případ optimalizovat.

Hádám, že si někdo myslel, že jsou chytří a že tím věci urychlují.

Bohužel, pokud není situace velmi dobře pochopena (je pravděpodobné, že nic takového neudělali), možná to všechno ještě zhoršilo. Dokumentace dokonce říká:

Obecně byste k tomu měli raději použít skutečnou zpětnou vazbu od profilu (-fprofile-arcs ), protože programátoři jsou notoricky špatní v předpovídání toho, jak jejich programy skutečně fungují. Existují však aplikace, ve kterých je obtížné tato data sbírat.

Obecně byste neměli používat __builtin_expect pokud:

  • Máte velmi skutečný problém s výkonem
  • Algoritmy v systému jste již náležitě optimalizovali
  • Máte údaje o výkonu, které podpoří vaše tvrzení, že nejpravděpodobnější je konkrétní případ

Linux
  1. Co se stalo s volným prostorem?

  2. Aktualizovat Gcc na verzi 6.3?

  3. Co znamená POSIX?

  1. Jaké jsou výchozí začleněné adresáře GCC?

  2. Co je to souborový systém NSFS?

  3. Co je zlatý linker?

  1. Linux vs. Unix:Jaký je rozdíl?

  2. Jaký je trik LD_PRELOAD?

  3. Jaký je uživatel debian-+?