GNU/Linux >> Znalost Linux >  >> Linux

Jaký je rozdíl mezi pravděpodobnými a nepravděpodobnými voláními v jádře?

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éž.


Linux
  1. Rozdíl mezi [[ $a ==Z* ]] a [ $a ==Z* ]?

  2. Jak fungují pravděpodobná/nepravděpodobná makra v linuxovém jádře a jaký je jejich přínos?

  3. Jaký je rozdíl mezi adduser a useradd?

  1. Jaký je rozdíl mezi voláním knihovny a voláním systému v Linuxu?

  2. Jaký je rozdíl mezi ovladači jádra a moduly jádra?

  3. Jaký je rozdíl mezi `su -` a `su --login`?

  1. Jaký je rozdíl mezi InnoDB a MyISAM?

  2. Jaký je rozdíl mezi strtok_r a strtok_s v C?

  3. Jaký je rozdíl mezi fsck a e2fsck?