GNU/Linux >> Tutoriels Linux >  >> Linux

Quelle est la différence entre les appels probables et improbables dans le noyau ?

Ce sont des conseils de compilation pour GCC. Ils sont utilisés dans les conditionnels pour indiquer au compilateur si une branche est susceptible d'être prise ou non. Cela peut aider le compilateur à établir le code de manière optimale pour le résultat le plus fréquent.

Ils sont utilisés comme ceci :

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

Il doit être utilisé avec beaucoup de précautions (c'est-à-dire basé sur les résultats réels du profilage des branches). Un indice erroné peut dégrader les performances (évidemment).

Quelques exemples de la façon dont le code peut être optimisé sont facilement trouvés en recherchant GCC __builtin_expect . Cet article de blog sur l'optimisation de gcc :__builtin_expect par exemple détaille un démontage avec.

Le type d'optimisations qui peuvent être faites est très spécifique au processeur. L'idée générale est que souvent, les processeurs exécuteront le code plus rapidement s'il ne se ramifie pas/saute partout. Plus il est linéaire et plus les branches sont prévisibles, plus il s'exécutera rapidement. (Cela est particulièrement vrai pour les processeurs avec des pipelines profonds par exemple.)

Ainsi, le compilateur émettra le code de sorte que la branche la plus probable n'impliquera pas de saut si c'est ce que le CPU cible préfère, par exemple.


Décompilons pour voir ce que GCC 4.8 en fait

Sans attente

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

Compiler et décompiler avec GCC 4.8.2 x86_64 Linux :

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

Sortie :

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'ordre des instructions en mémoire était inchangé :d'abord le printf puis puts et le retq retour.

Avec attente

Remplacez maintenant if (i) avec :

if (__builtin_expect(i, 0))

et on obtient :

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>

Le printf (compilé en __printf_chk ) a été déplacé à la toute fin de la fonction, après puts et le retour pour améliorer la prédiction de branche comme mentionné par d'autres réponses.

Donc, c'est fondamentalement la même chose que :

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

Cette optimisation n'a pas été faite avec -O0 .

Mais bonne chance pour écrire un exemple qui s'exécute plus rapidement avec __builtin_expect que sans, les processeurs sont vraiment intelligents ces jours-ci. Mes tentatives naïves sont là.

C++20 [[likely]] et [[unlikely]]

C++20 a standardisé ces éléments intégrés C++ :https://stackoverflow.com/questions/51797959/how-to-use-c20s-likely-unlikely-attribute-in-if-else-statement Ils seront probablement (un jeu de mots !) faire la même chose.


Linux
  1. Quelle est la différence entre strtok_r et strtok_s en C ?

  2. Quelle est la différence entre fsck et e2fsck ?

  3. Quelle est la différence entre adduser et useradd ?

  4. Quelle est la différence entre un appel Library et un appel System sous Linux ?

  5. Quelle est la différence entre les pilotes du noyau et les modules du noyau ?

Quelle est la différence entre les noyaux macOS et Linux

Quelle est la différence entre Linux et Unix ?

Qu'est-ce qu'un Hyperviseur ? Quelle est la différence entre les types 1 et 2 ?

Quelle est la différence entre curl et Wget ?

Quelle est la différence entre $(CC) et $CC ?

Quelle est la différence entre la route et la route IP ?