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


71

GCC 4.8变更日志草案中

G ++现在实现了C ++ 11 thread_local关键字;这与GNU__thread关键字的主要区别在于它允许动态初始化和销毁​​语义。不幸的是,此支持要求引用非函数局部thread_local变量,即使它们不需要动态初始化也需要运行时惩罚 ,因此用户可能希望继续使用__thread具有静态初始化语义的TLS变量。

这种运行时惩罚的本质和来源到底是什么?

显然,要支持非函数局部thread_local变量,在进入每个线程主线程之前都需要一个线程初始化阶段(就像全局变量有一个静态初始化阶段一样),但是它们是否指的是超出此范围的运行时代价? ?

粗略地说,gcc的thread_local新实现的体系结构是什么?


5
我真的认为GCC邮件列表是一个更好的发问地点(而且很可能会得到答案,尽管Jonathan Wakely和其他GCC / libstdc ++开发人员潜伏在这里并且可能了解更多)。尽管如此,有趣的问题。
Xeo 2012年

Answers:


49

(免责声明:我对GCC的内部了解不多,所以这也是有根据的猜测。)

thread_local在提交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;

编译*时,我们看到对引用的每次使用都会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。包装器仅在以下情况下生成:

  • 不是局部函数,并且,

    1. 它是 extern(上面显示的示例),或者
    2. 该类型具有非平凡的析构函数(不允许用于 __thread变量),或者
    3. 类型变量是通过非常量表达式初始化的(__thread变量也不允许)。

在所有其他用例中,其行为与相同__thread。这意味着,除非你有一些extern __thread变量,你可以取代所有__threadthread_local没有任何性能损失。


*:我使用-O0进行编译,因为内联函数会使函数边界不那么明显。即使我们打开-O3,那些初始化检查仍然保留。


5
那真是愚蠢。当然,这意味着您不必进行任何呼叫流分析就可以确定是否存在对的先前访问tls,但是即使是最幼稚的分析也可能会检测到第7行的访问绝对不能成为第一个访问。
MSalters 2012年

15
@MSalters,欢迎修补程序,如果可以改善的话!:)以gcc.gnu.org/ml/gcc/2012-10/msg00024.html开头的线程进行了一些相关的讨论
Jonathan Wakely 2013年

10

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();的调用。这是默认情况,因为您不受共享库使用方式的限制。

来源:Ulrich Drepper的ELF处理线程本地存储 https://www.akkadia.org/drepper/tls.pdf 该文本还列出了为受支持的目标平台生成的代码。


9

如果在当前TU中定义了变量,则内联将处理开销。我希望对于thread_local的大多数使用都是如此。

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


1
我的使用几乎总是thread_local通过类似的模式进行T& f() { thread_local t; return t; }。我正在使用gcc 4.7,所以我目前使用“替代方法”来实现我在此处编写的thread_local:stackoverflow.com/q/12049684/1131467。对于该功能,4.8实现的开销与我的4.7解决方案实现相比如何f
安德鲁·托马佐斯

这是4.7解决方法的直接链接:stackoverflow.com/a/12053862/1131467
Andrew Tomazos

发行说明条目是关于非函数局部变量的;对于您示例中的t这样的局部变量,4.8的实现应与您的解决方法相似或略有效率。
杰森·美林
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.