Was ist der Unterschied zwischen wahrscheinlichen und unwahrscheinlichen Aufrufen im Kernel?


11

Was ist der zwischen wahrscheinlichen und unwahrscheinlichen Aufrufen im Kernel? Beim Durchsuchen der Kernelquelle habe ich diese Aussagen gefunden.

# define likely(x)      __builtin_expect(!!(x), 1)
# define unlikely(x)    __builtin_expect(!!(x), 0)

Könnte jemand etwas Licht ins Dunkel bringen?


Dies ist wirklich eine Programmierfrage, die besser für Stack OVerflow geeignet ist .
Gilles 'SO - hör auf böse zu sein'

Antworten:


14

Sie sind Compiler-Hinweise für GCC. Sie werden in Bedingungen verwendet, um dem Compiler mitzuteilen, ob ein Zweig wahrscheinlich genommen wird oder nicht. Dies 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 (dh basierend auf den tatsächlichen Ergebnissen der Branchenprofilierung). Ein falscher Hinweis kann die Leistung (offensichtlich) beeinträchtigen.

Einige Beispiele, wie der Code optimiert werden kann, können leicht durch Suchen gefunden werden GCC __builtin_expect. Dieser Blog-Beitrag gcc-Optimierung: __builtin_expect beschreibt beispielsweise eine Demontage damit.

Die Art der Optimierungen, die durchgeführt werden können, ist sehr prozessorspezifisch. Die allgemeine Idee ist, dass Prozessoren Code häufig schneller ausführen, wenn er nicht überall verzweigt / springt. Je linearer es ist und je vorhersehbarer die Zweige sind, desto schneller läuft es. (Dies gilt insbesondere für Prozessoren mit tiefen Pipelines.)

Der Compiler gibt den Code also so aus, dass der wahrscheinlichste Zweig keinen Sprung beinhaltet, wenn dies beispielsweise die Ziel-CPU bevorzugt.


Was ist mit Einhörnern gemeint ? Ist es ein Fachbegriff oder nur ein Füllstoff?
Sen

Ich entfernte die Einhörner, um Verwirrung zu vermeiden.
Mat

Könnten Sie bitte näher darauf eingehen, dass der Compiler versuchen wird, das Codelayout für den Fall optimal zu gestalten ? Ich würde gerne wissen, wie es das macht.
Sen

fügte ein paar Informationen dazu hinzu. Es gibt keine allgemeine Möglichkeit, Code zu optimieren. Es ist alles sehr prozessorabhängig.
Mat

2

Lassen Sie uns dekompilieren, um zu sehen, was GCC 4.8 damit macht

Ohne zu erwarten

#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 printfund dann putsund die retqRückgabe.

Mit erwarten

Ersetzen Sie nun durch if (i):

if (__builtin_expect(i, 0))

und wir bekommen:

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>

Das printf(kompiliert nach __printf_chk) wurde nach putsund nach der Rückkehr an das Ende der Funktion verschoben, um die Verzweigungsvorhersage zu verbessern, 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 durchgeführt -O0.

Aber viel Glück beim Schreiben eines Beispiels, das mit und __builtin_expectohne schneller läuft. CPUs sind heutzutage wirklich schlau . Meine naiven Versuche sind hier .

C ++ 20 [[likely]]und[[unlikely]]

C ++ 20 hat diese C ++ - Integrationen standardisiert: /programming/51797959/how-to-use-c20s-likely-unlikely-attribute-in-if-else-statement Sie werden wahrscheinlich (a Wortspiel!) das Gleiche tun.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.