And*_*zos 64 c++ linux multithreading gcc c++11
G ++现在实现了C++ 11
thread_local关键字; 这与GNU__thread关键字的不同之处主要在于它允许动态初始化和销毁语义.不幸的是,这种支持需要对非函数局部thread_local变量的引用进行运行时惩罚, 即使它们不需要动态初始化,因此用户可能希望继续使用__thread具有静态初始化语义的TLS变量.
这种运行时惩罚的本质和来源究竟是什么?
显然,为了支持非函数局部thread_local变量,在进入每个线程main之前需要有一个线程初始化阶段(就像全局变量的静态初始化阶段一样),但是它们指的是一些超出该阶段的运行时惩罚?
粗略地说一下gcc的thread_local新实现的架构是什么?
ken*_*ytm 46
(免责声明:我对海湾合作委员会的内部情况了解不多,所以这也是一个有根据的猜测.)
动态thread_local初始化在commit 462819c中添加.其中一个变化是:
* semantics.c (finish_id_expression): Replace use of thread_local
variable with a call to its wrapper.
因此,运行时惩罚是,thread_local变量的每个引用都将成为函数调用.我们来看一个简单的测试用例:
// 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;
Run Code Online (Sandbox Code Playgroud)
当编译*时,我们看到引用的每次使用都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
Run Code Online (Sandbox Code Playgroud)
将它与__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
Run Code Online (Sandbox Code Playgroud)
thread_local但是,在每个用例中都不需要这个包装器.这可以从中揭示出来decl2.c.仅在以下情况下生成包装器:
它不是功能本地的,并且,
extern(上面显示的例子),或__thread变量),或__thread变量也不允许).在所有其他用例中,它的行为与__thread.这意味着,除非你有一些extern __thread变量,否则你可以__thread在thread_local没有任何性能损失的情况下替换所有变量.
*:我用-O0编译,因为内联器会使函数边界不太明显.即使我们转向-O3,这些初始化检查仍然存在.
如果变量在当前TU中定义,则内联器将负责开销.我希望大多数使用thread_local都是如此.
对于外部变量,如果程序员可以确定在非定义TU中不使用该变量需要触发动态初始化(因为该变量是静态初始化的,或者在定义TU中使用该变量之前将执行在另一个TU中的任何使用),他们可以使用-fno-extern-tls-init选项来避免这种开销.
C++ 11 thread_local与__thread说明符具有相同的运行时效果(__thread不是C标准thread_local的一部分; 是C++标准的一部分)
它取决于声明TLS变量(用__thread说明符声明)的位置.
-fPIC编译器选项编译)并且-ftls-model=initial-exec指定了编译器选项,则访问速度很快; 但是以下限制适用:无法通过dlopen/dlsym(动态加载)加载共享库,使用该库的唯一方法是在编译期间链接它(链接器选项-l<libraryname>)-fPIC编译器选项集)中声明了TLS变量,那么访问速度非常慢,因为假定了一般的动态TLS模型 - 这里每次访问TLS变量都会导致调用_tls_get_addr(); 这是默认情况,因为您不限制使用共享库的方式.来源:线程局部存储的ELF处理Ulrich Drepper https://www.akkadia.org/drepper/tls.pdf 此文本还列出了为支持的目标平台生成的代码.