Depuis le projet de changelog GCC 4.8 :
G ++ implémente maintenant le C++ 11
thread_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-localesthread_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?
(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,
extern
(l'exemple illustré ci-dessus), ou__thread
), Ou__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.
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.
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.
-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>
)-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.