web-dev-qa-db-fra.com

Quelle est la pénalité de performance des variables thread_local C ++ 11 dans GCC 4.8?

Depuis le projet de changelog GCC 4.8 :

G ++ implémente maintenant le C++ 11thread_local mot-clé; cela diffère du GNU __thread mot clé principalement en ce qu'il permet une sémantique d'initialisation et de destruction dynamique. Malheureusement, cette prise en charge nécessite une pénalité d'exécution pour les références à des non-fonction-locales thread_local variables même si elles n'ont pas besoin d'une initialisation dynamique, les utilisateurs peuvent donc continuer à utiliser __thread pour les variables TLS avec une sémantique d'initialisation statique.

Quelle est précisément la nature et l'origine de cette pénalité d'exécution?

De toute évidence, pour prendre en charge la fonction locale non fonctionnelle thread_local variables, il doit y avoir une phase d'initialisation du thread avant l'entrée dans chaque thread principal (tout comme il existe une phase d'initialisation statique pour les variables globales), mais font-elles référence à une pénalité d'exécution au-delà?

En gros, quelle est l'architecture de la nouvelle implémentation de thread_local par gcc?

63
Andrew Tomazos

(Avertissement: je ne sais pas grand-chose sur les éléments internes de GCC, c'est donc également une supposition éclairée.)

L'initialisation dynamique thread_local Est ajoutée dans commit 462819c . L'un des changements est le suivant:

* semantics.c (finish_id_expression): Replace use of thread_local
variable with a call to its wrapper.

La pénalité d'exécution est donc que chaque référence de la variable thread_local Deviendra un appel de fonction. Vérifions avec un cas de test simple:

// 3.cpp
extern thread_local int tls;    
int main() {
    tls += 37;   // line 6
    tls &= 11;   // line 7
    tls ^= 3;    // line 8
    return 0;
}

// 4.cpp

thread_local int tls = 42;

Une fois compilé *, nous voyons que chaque utilisation de la référence tls devient un appel de fonction à _ZTW3tls, Qui paresseusement initialisez la variable une fois:

00000000004005b0 <main>:
main():
  4005b0:   55                          Push   rbp
  4005b1:   48 89 e5                    mov    rbp,rsp
  4005b4:   e8 26 00 00 00              call   4005df <_ZTW3tls>    // line 6
  4005b9:   8b 10                       mov    edx,DWORD PTR [rax]
  4005bb:   83 c2 25                    add    edx,0x25
  4005be:   89 10                       mov    DWORD PTR [rax],edx
  4005c0:   e8 1a 00 00 00              call   4005df <_ZTW3tls>    // line 7
  4005c5:   8b 10                       mov    edx,DWORD PTR [rax]
  4005c7:   83 e2 0b                    and    edx,0xb
  4005ca:   89 10                       mov    DWORD PTR [rax],edx
  4005cc:   e8 0e 00 00 00              call   4005df <_ZTW3tls>    // line 8
  4005d1:   8b 10                       mov    edx,DWORD PTR [rax]
  4005d3:   83 f2 03                    xor    edx,0x3
  4005d6:   89 10                       mov    DWORD PTR [rax],edx
  4005d8:   b8 00 00 00 00              mov    eax,0x0              // line 9
  4005dd:   5d                          pop    rbp
  4005de:   c3                          ret

00000000004005df <_ZTW3tls>:
_ZTW3tls():
  4005df:   55                          Push   rbp
  4005e0:   48 89 e5                    mov    rbp,rsp
  4005e3:   b8 00 00 00 00              mov    eax,0x0
  4005e8:   48 85 c0                    test   rax,rax
  4005eb:   74 05                       je     4005f2 <_ZTW3tls+0x13>
  4005ed:   e8 0e fa bf ff              call   0 <tls> // initialize the TLS
  4005f2:   64 48 8b 14 25 00 00 00 00  mov    rdx,QWORD PTR fs:0x0
  4005fb:   48 c7 c0 fc ff ff ff        mov    rax,0xfffffffffffffffc
  400602:   48 01 d0                    add    rax,rdx
  400605:   5d                          pop    rbp
  400606:   c3                          ret

Comparez-le avec la version __thread, Qui n'aura pas ce wrapper supplémentaire:

00000000004005b0 <main>:
main():
  4005b0:   55                          Push   rbp
  4005b1:   48 89 e5                    mov    rbp,rsp
  4005b4:   48 c7 c0 fc ff ff ff        mov    rax,0xfffffffffffffffc // line 6
  4005bb:   64 8b 00                    mov    eax,DWORD PTR fs:[rax]
  4005be:   8d 50 25                    lea    edx,[rax+0x25]
  4005c1:   48 c7 c0 fc ff ff ff        mov    rax,0xfffffffffffffffc
  4005c8:   64 89 10                    mov    DWORD PTR fs:[rax],edx
  4005cb:   48 c7 c0 fc ff ff ff        mov    rax,0xfffffffffffffffc // line 7
  4005d2:   64 8b 00                    mov    eax,DWORD PTR fs:[rax]
  4005d5:   89 c2                       mov    edx,eax
  4005d7:   83 e2 0b                    and    edx,0xb
  4005da:   48 c7 c0 fc ff ff ff        mov    rax,0xfffffffffffffffc
  4005e1:   64 89 10                    mov    DWORD PTR fs:[rax],edx
  4005e4:   48 c7 c0 fc ff ff ff        mov    rax,0xfffffffffffffffc // line 8
  4005eb:   64 8b 00                    mov    eax,DWORD PTR fs:[rax]
  4005ee:   89 c2                       mov    edx,eax
  4005f0:   83 f2 03                    xor    edx,0x3
  4005f3:   48 c7 c0 fc ff ff ff        mov    rax,0xfffffffffffffffc
  4005fa:   64 89 10                    mov    DWORD PTR fs:[rax],edx
  4005fd:   b8 00 00 00 00              mov    eax,0x0                // line 9
  400602:   5d                          pop    rbp
  400603:   c3                          ret

Ce wrapper n'est cependant pas nécessaire dans tous les cas d'utilisation de thread_local. Cela peut être révélé à partir de decl2.c . Le wrapper est généré uniquement lorsque:

  • Ce n'est pas fonction-local, et,

    1. Il s'agit de extern (l'exemple illustré ci-dessus), ou
    2. Le type a un destructeur non trivial (qui n'est pas autorisé pour les variables __thread), Ou
    3. La variable type est initialisée par une expression non constante (ce qui n'est pas non plus autorisé pour les variables __thread).

Dans tous les autres cas d'utilisation, il se comporte de la même manière que __thread. Cela signifie que, sauf si vous avez des variables extern __thread, Vous pouvez remplacer tous les __thread Par thread_local Sans aucune perte de performances.


*: J'ai compilé avec -O0 car l'inliner rendra la frontière de fonction moins visible. Même si nous passons à -O3, ces vérifications d'initialisation demeurent.

44
kennytm

Si la variable est définie dans le TU en cours, l'inliner prendra soin de la surcharge. Je m'attends à ce que cela soit vrai pour la plupart des utilisations de thread_local.

Pour les variables externes, si le programmeur peut être sûr qu'aucune utilisation de la variable dans un TU non défini doit déclencher l'initialisation dynamique (soit parce que la variable est initialisée statiquement, soit dans la définition TU sera exécuté avant toute utilisation dans une autre TU), ils peuvent éviter ce surcoût avec l'option -fno-extern-tls-init.

8
Jason Merrill

C++ 11 thread_local a le même effet d'exécution que le spécificateur __thread (__thread Ne fait pas partie de la norme C; thread_local Fait partie de la norme C++)

cela dépend où la variable TLS (déclarée avec le spécificateur __thread) est déclarée.

  • si la variable TLS est déclarée dans un exécutable, l'accès est rapide
  • si la variable TLS est déclarée dans le code de bibliothèque partagée (compilé avec l'option de compilation -fPIC) et que l'option de compilation -ftls-model=initial-exec est spécifiée, l'accès est rapide; cependant la limitation suivante s'applique: la bibliothèque partagée ne peut pas être chargée via dlopen/dlsym (chargement dynamique), la seule façon d'utiliser la bibliothèque est de la lier avec elle pendant la compilation (option de l'éditeur de liens -l<libraryname>)
  • si la variable TLS est déclarée dans une bibliothèque partagée (jeu d'options du compilateur -fPIC), l'accès est très lent, car le modèle TLS dynamique général est supposé - ici, chaque accès à une variable TLS entraîne un appel à la fonction _tls_get_addr(); c'est le cas par défaut car vous n'êtes pas limité dans la façon dont la bibliothèque partagée est utilisée.

Sources: ELF Handling For Thread-Local Storage par Ulrich Drepper https://www.akkadia.org/drepper/tls.pdf ce texte répertorie également le code généré pour les plates-formes cibles prises en charge.

7
MichaelMoser