Sie sind Compiler-Hinweise für GCC. Sie werden in Bedingungen verwendet, um dem Compiler mitzuteilen, ob eine Verzweigung wahrscheinlich genommen wird oder nicht. Es kann dem Compiler helfen, den Code so festzulegen, dass er für das häufigste Ergebnis optimal ist.
Sie werden wie folgt verwendet:
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();
}
Es sollte mit großer Sorgfalt verwendet werden (d. h. auf der Grundlage tatsächlicher Branchenprofilierungsergebnisse). Ein falscher Hinweis kann (offensichtlich) die Leistung beeinträchtigen.
Einige Beispiele, wie der Code optimiert werden kann, finden Sie leicht, indem Sie nach GCC __builtin_expect
suchen . Dieser Blog-Beitrag gcc-Optimierung:__builtin_expect beschreibt zum Beispiel eine Demontage damit.
Die Art der Optimierungen, die durchgeführt werden können, ist sehr prozessorspezifisch. Die allgemeine Idee ist, dass Prozessoren Code oft schneller ausführen, wenn er nicht überall verzweigt / springt. Je linearer es ist und je vorhersehbarer die Verzweigungen sind, desto schneller läuft es. (Dies gilt beispielsweise insbesondere für Prozessoren mit tiefen Pipelines.)
Der Compiler gibt den Code also so aus, dass die wahrscheinlichste Verzweigung keinen Sprung beinhaltet, wenn dies zum Beispiel die Ziel-CPU bevorzugt.
Lassen Sie uns dekompilieren, um zu sehen, was GCC 4.8 damit macht
Ohne Erwartung
#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;
}
Kompilieren und dekompilieren Sie mit GCC 4.8.2 x86_64 Linux:
gcc -c -O3 -std=gnu11 main.c
objdump -dr main.o
Ausgabe:
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
Die Befehlsreihenfolge im Speicher blieb unverändert:zuerst die printf
und dann puts
und die retq
zurück.
Mit Erwartung
Ersetzen Sie nun if (i)
mit:
if (__builtin_expect(i, 0))
und wir erhalten:
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>
Die printf
(kompiliert zu __printf_chk
) wurde an das Ende der Funktion verschoben, nach puts
und die Rückkehr zur Verbesserung der Verzweigungsvorhersage, wie in anderen Antworten erwähnt.
Es ist also im Grunde dasselbe wie:
int i = !time(NULL);
if (i)
goto printf;
puts:
puts("a");
return 0;
printf:
printf("%d\n", i);
goto puts;
Diese Optimierung wurde nicht mit -O0
durchgeführt .
Aber viel Glück beim Schreiben eines Beispiels, das mit __builtin_expect
schneller läuft als ohne, CPUs sind heutzutage wirklich schlau. Meine naiven Versuche sind hier.
C++20 [[likely]]
und [[unlikely]]
C++20 hat diese integrierten C++-Ins standardisiert:https://stackoverflow.com/questions/51797959/how-to-use-c20s-likely-unlikely-attribute-in-if-else-statement Sie werden wahrscheinlich (a Wortspiel!) dasselbe tun.