(免责声明:我对GCC的内部了解不多,所以这也是有根据的猜测。)
thread_local
在提交462819c中添加了动态初始化。更改之一是:
* semantics.c (finish_id_expression): Replace use of thread_local
variable with a call to its wrapper.
因此,运行时的代价是,thread_local
变量的每个引用都将成为函数调用。让我们用一个简单的测试用例进行检查:
extern thread_local int tls;
int main() {
tls += 37;
tls &= 11;
tls ^= 3;
return 0;
}
thread_local int tls = 42;
编译*时,我们看到对引用的每次使用都会tls
成为对的函数调用_ZTW3tls
,该函数会延迟一次初始化变量:
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
将其与__thread
没有额外包装的版本进行比较:
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
thread_local
尽管在每种使用情况下都不需要此包装器。这可以从中揭示出来decl2.c
。包装器仅在以下情况下生成:
它不是局部函数,并且,
- 它是
extern
(上面显示的示例),或者
- 该类型具有非平凡的析构函数(不允许用于
__thread
变量),或者
- 类型变量是通过非常量表达式初始化的(
__thread
变量也不允许)。
在所有其他用例中,其行为与相同__thread
。这意味着,除非你有一些extern __thread
变量,你可以取代所有__thread
的thread_local
没有任何性能损失。
*:我使用-O0进行编译,因为内联函数会使函数边界不那么明显。即使我们打开-O3,那些初始化检查仍然保留。