GCC 4.8中C++ 11 thread_local变量的性能损失是多少?

And*_*zos 64 c++ linux multithreading gcc c++11

来自GCC 4.8草案变更日志:

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.仅在以下情况下生成包装器:

  • 不是功能本地的,并且,

    1. 它是extern(上面显示的例子),或
    2. 该类型有一个非平凡的析构函数(不允许__thread变量),或
    3. 类型变量由非常量表达式初始化(__thread变量也不允许).

在所有其他用例中,它的行为与__thread.这意味着,除非你有一些extern __thread变量,否则你可以__threadthread_local没有任何性能损失的情况下替换所有变量.


*:我用-O0编译,因为内联器会使函数边界不太明显.即使我们转向-O3,这些初始化检查仍然存在.

  • @MSalters,补丁欢迎,如果你可以改进它!:)从http://gcc.gnu.org/ml/gcc/2012-10/msg00024.html开始的主题有一些相关的讨论 (15认同)
  • 这非常愚蠢.当然,这意味着您无需进行任何类型的呼叫流分析来确定是否存在先前对"tls"的访问,但即使是最天真的分析也会检测到第7行的访问绝对不能成为第一次访问. (5认同)

Jas*_*ill 8

如果变量在当前TU中定义,则内联器将负责开销.我希望大多数使用thread_local都是如此.

对于外部变量,如果程序员可以确定在非定义TU中不使用该变量需要触发动态初始化(因为该变量是静态初始化的,或者在定义TU中使用该变量之前将执行在另一个TU中的任何使用),他们可以使用-fno-extern-tls-init选项来避免这种开销.

  • 我对“thread_local”的使用几乎总是通过类似“T&amp; f() { thread_local t; 返回t;}`。我使用的是 gcc 4.7,所以我目前使用“解决方法”来实现我在这里编写的 thread_local:http://stackoverflow.com/q/12049684/1131467。对于“f”函数的情况,4.8 实现的开销与我的 4.7 解决方法实现相比如何? (3认同)

Mic*_*ser 7

C++ 11 thread_local与__thread说明符具有相同的运行时效果(__thread不是C标准thread_local的一部分; 是C++标准的一部分)

它取决于声明TLS变量(用__thread说明符声明)的位置.

  • 如果在可执行文件中声明TLS变量,则访问速度很快
  • 如果在共享库代码中声明了TLS变量(使用-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 此文本还列出了为支持的目标平台生成的代码.