Aus dem GCC 4.8-Änderungsprotokollentwurf :
G ++ implementiert jetzt das Schlüsselwort C ++ 11
thread_local
; Dies unterscheidet sich vom GNU-__thread
Schlüsselwort hauptsächlich dadurch, dass es eine dynamische Initialisierungs- und Zerstörungssemantik ermöglicht. Leider erfordert diese Unterstützung eine Laufzeitstrafe für Verweise auf nicht funktionslokalethread_local
Variablen, auch wenn sie keine dynamische Initialisierung benötigen. Daher möchten Benutzer möglicherweise weiterhin__thread
TLS-Variablen mit statischer Initialisierungssemantik verwenden.
Was genau ist die Art und der Ursprung dieser Laufzeitstrafe?
Um nicht funktionslokale thread_local
Variablen zu unterstützen, muss natürlich vor dem Eintritt in jede Thread-Hauptphase eine Thread-Initialisierungsphase stattfinden (genau wie es eine statische Initialisierungsphase für globale Variablen gibt) ?
Was ist grob gesagt die Architektur der neuen Implementierung von thread_local durch gcc?
Antworten:
(Haftungsausschluss: Ich weiß nicht viel über die Interna von GCC, daher ist dies auch eine fundierte Vermutung.)
Die dynamische
thread_local
Initialisierung wird in Commit 462819c hinzugefügt . Eine der Änderungen ist:Die Laufzeitstrafe besteht also darin, dass jede Referenz der
thread_local
Variablen zu einem Funktionsaufruf wird. Lassen Sie uns mit einem einfachen Testfall überprüfen:// 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;
Beim Kompilieren * sehen wir, dass jede Verwendung der
tls
Referenz zu einem Funktionsaufruf wird_ZTW3tls
, der die Variable einmal träge initialisiert: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
Vergleichen Sie es mit der
__thread
Version, die diesen zusätzlichen Wrapper nicht hat: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
Dieser Wrapper wird jedoch nicht in jedem Anwendungsfall benötigt
thread_local
. Dies kann aus entnommen werdendecl2.c
. Der Wrapper wird nur generiert, wenn:Es ist nicht funktionslokal und,
extern
(das oben gezeigte Beispiel) oder__thread
Variablen nicht zulässig ist) oder__thread
Variablen nicht zulässig ist).In allen anderen Anwendungsfällen verhält es sich genauso wie
__thread
. Das heißt, es sei denn , Sie einige habenextern __thread
Variablen, könnten Sie alle ersetzen__thread
durch ,thread_local
ohne Leistungsverlust.*: Ich habe mit -O0 kompiliert, weil der Inliner die Funktionsgrenze weniger sichtbar macht. Selbst wenn wir auf -O3 auftauchen, bleiben diese Initialisierungsprüfungen bestehen.
quelle
tls
, aber selbst die naivste Analyse hätte festgestellt, dass der Zugriff in Zeile 7 absolut nicht der erste Zugriff sein kann.C ++ 11 thread_local hat den gleichen Laufzeiteffekt wie der __thread-Bezeichner (
__thread
ist nicht Teil des C-Standards;thread_local
ist Teil des C ++ - Standards)Dies hängt davon ab, wo die TLS-Variable (mit dem Bezeichner deklariert
__thread
) deklariert ist.-fPIC
Compiler-Option) und die-ftls-model=initial-exec
Compiler-Option angegeben ist, ist der Zugriff schnell. Es gilt jedoch die folgende Einschränkung: Die gemeinsam genutzte Bibliothek kann nicht über dlopen / dlsym geladen werden (dynamisches Laden). Die einzige Möglichkeit, die Bibliothek zu verwenden, besteht darin, sie während der Kompilierung zu verknüpfen (Linker-Option-l<libraryname>
).-fPIC
Compiler-Optionssatz), ist der Zugriff sehr langsam, da das allgemeine dynamische TLS-Modell angenommen wird. Hier führt jeder Zugriff auf eine TLS-Variable zu einem Aufruf von_tls_get_addr()
. Dies ist der Standardfall, da Sie in der Art und Weise, wie die gemeinsam genutzte Bibliothek verwendet wird, nicht eingeschränkt sind.Quellen: ELF-Handling für threadlokalen Speicher von Ulrich Drepper https://www.akkadia.org/drepper/tls.pdf In diesem Text wird auch der Code aufgeführt, der für die unterstützten Zielplattformen generiert wird.
quelle
Wenn die Variable in der aktuellen TU definiert ist, kümmert sich der Inliner um den Overhead. Ich gehe davon aus, dass dies für die meisten Verwendungen von thread_local gilt.
Wenn der Programmierer bei externen Variablen sicher sein kann, dass keine Verwendung der Variablen in einer nicht definierenden TU eine dynamische Initialisierung auslösen muss (entweder weil die Variable statisch initialisiert ist oder eine Verwendung der Variablen in der definierenden TU zuvor ausgeführt wird) Bei Verwendung in einer anderen TU) können sie diesen Overhead mit der Option -fno-extern-tls-init vermeiden.
quelle
thread_local
ein Muster wieT& f() { thread_local t; return t; }
. Ich verwende gcc 4.7 und verwende derzeit eine "Problemumgehung", um thread_local zu implementieren, die ich hier geschrieben habe: stackoverflow.com/q/12049684/1131467 . Wie ist der Overhead der 4.8-Implementierung im Vergleich zu meiner 4.7-Workaround-Implementierung für den Fall derf
Funktion?