GNU/Linux >> LINUX-Kenntnisse >  >> Linux

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

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.


Linux
  1. Was ist der Unterschied zwischen InnoDB und MyISAM?

  2. Was ist der Unterschied zwischen ls und l?

  3. Was ist der Unterschied zwischen Kerneltreibern und Kernelmodulen?

  4. Was ist der Unterschied zwischen `su -` und `su --login`?

  5. Was ist der Unterschied zwischen Route und IP-Route?

Was ist der Unterschied zwischen macOS- und Linux-Kernels

Was ist der Unterschied zwischen Linux und Unix?

Was ist ein Hypervisor? Was ist der Unterschied zwischen Typ 1 und 2?

Was ist der Unterschied zwischen Curl und Wget?

Was ist der Unterschied zwischen $(CC) und $CC?

Was ist der Unterschied zwischen unlink und rm?