【问题标题】:What is the performance penalty of C++11 thread_local variables in GCC 4.8?GCC 4.8 中 C++11 线程局部变量的性能损失是什么?
【发布时间】:2012-10-17 20:20:58
【问题描述】:

来自GCC 4.8 draft changelog

G++ 现在实现了 C++11 thread_local 关键字;这不同于 GNU __thread 关键字主要是因为它允许动态 初始化和销毁​​语义。不幸的是,这种支持 对非函数本地的引用需要运行时惩罚 thread_local 变量即使它们不需要动态初始化, 因此用户可能希望继续将__thread 用于 TLS 变量 静态初始化语义。

这种运行时惩罚的性质和来源究竟是什么?

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

gcc新实现的thread_local大概是什么架构?

【问题讨论】:

  • 我真的认为 GCC 邮件列表会是一个更好的提问场所(并且很可能会得到答案,尽管 Jonathan Wakely 和其他 GCC/libstdc++ 开发人员潜伏在这里并且可能知道更多)。不过这个问题很有趣。
  • gcc.gnu.org/ml/gcc/2012-10/msg00024.html开头的线程中有一些相关的讨论

标签: c++ linux multithreading gcc c++11


【解决方案1】:

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

在提交462819c 中添加了动态thread_local 初始化。其中一项变化是:

* 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 变量,否则您可以将所有 __thread 替换为 thread_local 而不会损失任何性能。


*:我使用 -O0 编译,因为内联会使函数边界不那么明显。即使我们达到 -O3,这些初始化检查仍然存在。

【讨论】:

  • 这太愚蠢了。当然,这意味着您不必进行任何类型的呼叫流分析来确定是否存在对 tls 的先前访问,但即使是最天真的分析也会检测到第 7 行的访问绝对不能是第一次访问.
  • @MSalters,如果你能改进它,欢迎使用补丁! :) 以gcc.gnu.org/ml/gcc/2012-10/msg00024.html 开头的帖子有一些相关的讨论
【解决方案2】:

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

这取决于声明 TLS 变量(使用 __thread 说明符声明)的位置。

  • 如果在可执行文件中声明了 TLS 变量,那么访问速度很快
  • 如果在共享库代码中声明了 TLS 变量(使用-fPIC 编译器选项编译)并指定了-ftls-model=initial-exec 编译器选项,则访问速度很快;但是存在以下限制:无法通过 dlopen/dlsym(动态加载)加载共享库,使用该库的唯一方法是在编译期间与其链接(链接器选项 -l&lt;libraryname&gt;
  • 如果在共享库中声明了 TLS 变量(-fPIC 编译器选项集),则访问速度非常慢,因为假定了一般动态 TLS 模型 - 这里每次访问 TLS 变量都会导致调用 _tls_get_addr() ;这是默认情况,因为您不受使用共享库的方式限制。

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 我对@9​​87654323@ 的使用几乎总是通过T&amp; f() { thread_local t; return t; } 之类的模式。我正在使用 gcc 4.7,所以我目前使用“解决方法”来实现我在这里写的 thread_local:stackoverflow.com/q/12049684/1131467。对于f 函数,4.8 实现的开销与我的 4.7 解决方法实现相比如何?
    • 这里是 4.7 解决方法的直接链接:stackoverflow.com/a/12053862/1131467
    • 发行说明条目正在讨论非函数局部变量;对于您示例中的局部变量(如 t),4.8 实现应该与您的解决方法相似或略高效。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-01
    相关资源
    最近更新 更多