Qual è la differenza tra chiamate probabili e improbabili in Kernel?


11

Qual è la differenza tra chiamate probabili e improbabili in Kernel. Durante la ricerca nel sorgente del kernel ho trovato queste dichiarazioni.

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

Qualcuno potrebbe farci luce?


Questa è davvero una domanda di programmazione, più adatta per Stack OVerflow .
Gilles 'SO- smetti di essere malvagio' il

Risposte:


14

Sono suggerimenti per il compilatore di GCC. Sono usati nei condizionali per dire al compilatore se è probabile che si prenda o meno un ramo. Può aiutare il compilatore a stabilire il codice in modo ottimale per il risultato più frequente.

Sono usati in questo modo:

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();
}

Dovrebbe essere usato con grande cura (cioè sulla base dei risultati effettivi della profilazione del ramo). Un suggerimento sbagliato può degradare le prestazioni (ovviamente).

Alcuni esempi di come il codice può essere ottimizzato si trovano facilmente cercando GCC __builtin_expect. Questo blog pubblica l' ottimizzazione di gcc: __builtin_expect, ad esempio, descrive in dettaglio uno smontaggio con esso.

Il tipo di ottimizzazioni che è possibile eseguire è molto specifico del processore. L'idea generale è che spesso i processori eseguiranno il codice più velocemente se non si dirama / salta dappertutto. Più è lineare e più sono prevedibili i rami, più veloce sarà. (Ciò è particolarmente vero per i processori con condotte profonde, ad esempio.)

Quindi il compilatore emetterà il codice in modo tale che il ramo più probabile non comporti un salto se questo è ciò che preferisce la CPU di destinazione, ad esempio.


Cosa si intende per unicorni ? È un termine tecnico o solo un riempitivo?
Sen

Ho rimosso gli unicorni per evitare confusione.
Mat

Potresti per favore approfondire il compilatore proverà a rendere il layout del codice ottimale per il caso ? Vorrei sapere come funziona.
Sen

aggiunto un po 'di informazioni al riguardo. non esiste un modo generale per ottimizzare il codice, dipende tutto dal processore.
Mat

2

Decompiliamo per vedere cosa fa GCC 4.8

Senza aspettarsi

#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;
}

Compilare e decompilare con GCC 4.8.2 x86_64 Linux:

gcc -c -O3 -std=gnu11 main.c
objdump -dr main.o

Produzione:

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

L'ordine delle istruzioni in memoria è rimasto invariato: prima il printfe poi putse il retqritorno.

Con aspettiamo

Ora sostituisci if (i)con:

if (__builtin_expect(i, 0))

e otteniamo:

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>

Il printf(compilato in __printf_chk) è stato spostato alla fine della funzione, dopo putse il ritorno per migliorare la previsione del ramo come menzionato da altre risposte.

Quindi è sostanzialmente lo stesso di:

int i = !time(NULL);
if (i)
    goto printf;
puts:
puts("a");
return 0;
printf:
printf("%d\n", i);
goto puts;

Questa ottimizzazione non è stata eseguita -O0.

Ma buona fortuna a scrivere un esempio che corre più veloce __builtin_expectche senza, le CPU sono davvero intelligenti in quei giorni . I miei ingenui tentativi sono qui .

C ++ 20 [[likely]]e[[unlikely]]

C ++ 20 ha standardizzato questi built-in C ++: /programming/51797959/how-to-use-c20s-likely-unlikely-attribute-in-if-else-statement Probabilmente (a gioco di parole!) fai la stessa cosa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.