Jsou to rady kompilátoru pro GCC. Používají se v podmíněných podmínkách, aby řekli kompilátoru, zda větev bude pravděpodobně přijata nebo ne. Může to pomoci kompilátoru sestavit kód takovým způsobem, který je optimální pro nejčastější výsledek.
Používají se takto:
if (likely(some_condition)) {
// the compiler will try and make the code layout optimal for the case
// where some_condition is true, i.e. where this block is run
most_likely_action();
} else {
// this block is less frequently used
corner_case();
}
Měl by být používán s velkou opatrností (tj. na základě skutečných výsledků profilování větví). Špatná nápověda může snížit výkon (samozřejmě).
Některé příklady toho, jak lze kód optimalizovat, lze snadno najít vyhledáním GCC __builtin_expect
. Tento blogový příspěvek gcc optimalizace:__builtin_expect například podrobnosti o demontáži s ním.
Druh optimalizací, které lze provést, je velmi specifický pro procesor. Obecná myšlenka je, že procesory často poběží kód rychleji, pokud se nerozvětvuje/neskáče všude. Čím je lineárnější a čím jsou větve předvídatelnější, tím rychleji poběží. (To platí zejména například pro procesory s hlubokými kanály.)
Kompilátor tedy vydá kód tak, že nejpravděpodobnější větev nebude zahrnovat skok, pokud to například cílový CPU preferuje.
Pojďme provést dekompilaci, abychom viděli, co s tím GCC 4.8 dělá
Bez očekávání
#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;
}
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 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
Pořadí instrukcí v paměti bylo nezměněno:nejprve printf
a poté puts
a retq
vrátit.
S očekáváním
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 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
(zkompilováno do __printf_chk
) byl přesunut na úplný konec funkce, za puts
a návrat ke zlepšení predikce větvení, jak je zmíněno v jiných odpovědích.
Takže je to v podstatě stejné jako:
int i = !time(NULL);
if (i)
goto printf;
puts:
puts("a");
return 0;
printf:
printf("%d\n", i);
goto puts;
Tato optimalizace nebyla provedena s -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é moduly C++:https://stackoverflow.com/questions/51797959/how-to-use-c20s-likely-unlikely-attribute-in-if-else-statement Pravděpodobně budou (a slovní hříčka!) udělejte totéž.