C ++ 11 thread_localvariables在GCC 4.8中的性能损失是多less?
从GCC 4.8草案变更日志 :
G ++现在实现了C ++ 11的
thread_local
关键字; 这与GNU__thread
关键字的区别主要在于它允许dynamic初始化和销毁语义。 不幸的是,即使不需要dynamic初始化,对于非函数本地thread_local
variables的引用,这种支持也需要运行时间的惩罚,因此用户可能希望继续使用具有静态初始化语义的TLSvariables的__thread
。
这个运行时间惩罚的性质和起源究竟是什么?
显然,为了支持非函数本地的thread_local
variables,需要在进入每个线程main之前进行一个线程初始化阶段(就像全局variables有一个静态的初始化阶段一样),但是它们是否指向一些运行时间惩罚那?
粗略地说,gcc的thread_local的新实现的体系结构是什么?
(声明:我对GCC的内部知识不太了解,所以这也是一个有教养的猜测。)
在提交462819c中添加了dynamicthread_local
初始化。 其中一个变化是:
* semantics.c (finish_id_expression): Replace use of thread_local
variable with a call to its wrapper.
所以运行时间的惩罚是, thread_local
variables的每个引用都将成为一个函数调用。 我们来看一个简单的testing用例:
// 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;
当编译*时,我们看到tls
引用的每一次使用都变成了对_ZTW3tls
一个函数调用,它懒_ZTW3tls
地初始化了这个variables:
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
发现。 包装仅在以下情况下生成:
-
这不是function本地的,而且,
- 这是
extern
(上面的例子),或者 - 该types有一个不平凡的析构函数(不允许用于
__thread
variables),或者 - typesvariables由一个非常量expression式初始化(
__thread
variables也不允许)。
- 这是
在所有其他用例中,它的行为与__thread
相同。 这意味着,除非你有一些extern __thread
variables,你可以用thread_local
replace所有的__thread
,而不会损失性能。
*:我使用-O0进行编译,因为内联将使得函数边界变得更加可见。 即使我们转到-O3,那些初始化检查依然存在。
如果在当前TU中定义variables,则内联将负责开销。 我期望这将是thread_local的大部分用法。
对于externvariables,如果程序员可以确定在非定义的TU中不使用variables需要触发dynamic初始化(或者是因为variables是静态初始化的,或者是在定义的TU中使用variables之前在另一个TU中的任何用途),他们可以通过-fno-extern-tls-init选项避免这种开销。
C ++ 11 thread_local与__thread说明__thread
具有相同的运行时效果( __thread
不是C标准的一部分; thread_local
是C ++标准的一部分)
它取决于声明TLSvariables(使用__thread
说明符声明)的位置。
- 如果在可执行文件中声明TLSvariables,则访问速度很快
- 如果在共享库代码中声明了TLSvariables(使用
-fPIC
编译器选项编译),并-ftls-model=initial-exec
编译器选项,则访问速度很快; 但以下限制适用:共享库不能通过dlopen / dlsym(dynamic加载)加载,使用该库的唯一方法是在编译期间链接它(链接器选项-l<libraryname>
) - 如果TLSvariables在共享库(
-fPIC
编译器选项集)中声明,那么访问非常缓慢,因为假定通用的dynamicTLS模型 – 在这里每个对TLSvariables的访问都会导致对_tls_get_addr()
的调用。 这是默认情况,因为您不限制共享库的使用方式。
来源:ELF处理线程本地存储由Ulrich Drepper https://www.akkadia.org/drepper/tls.pdf此文本还列出了为支持的目标平台生成的代码。;
- C ++ 11 std :: bind和boost :: bind之间的区别
- 什么是智能指针,什么时候该用?
- 带有包扩展的variablesfunction模板不在最后一个参数中
- 在C ++ 0x中优化掉“while(1);”
- 当decltype应用于它们时,哪些expression式产生引用types?
- 在头文件中使用lambda是否违反了ODR?
- C ++ 11:我可以从多个参数到元组,但我可以从元组去多个参数?
- selectstd :: map和std :: unordered_map
- 为什么在Herb Sutter的CppCon 2014演讲中不推荐使用setter成员函数(回到基础:现代C ++风格)?