如何减少或消除__tls_init调用?

The*_*Vee 6 optimization g++ internal thread-local-storage

我正在使用依赖的第三方库thread_local.这导致我的程序__tls_init()重复调用,即使在某些周期的每次迭代中(我还没有检查所有这些),尽管thread_local变量已经被同一函数中早期的另一个调用无条件地初始化(事实上,在接近开始时)整个节目).

__tls_init()我的第一个指示x86_64

cmpb    $0, %fs:__tls_guard@tpoff
je      .L530
ret
.L530:
pushq   %rbp
pushq   %rbx
subq    (some stack space), %rsp
movb    $1, %fs:__tls_guard@tpoff
Run Code Online (Sandbox Code Playgroud)

所以第一次按每个线程调用它时,值at %fs:__tls_guard@tpoff设置为,1并且进一步调用立即返回.但是,这意味着call每次thread_local访问变量时都会产生所有开销,对吧?

请注意,这是一个静态链接(实际上是生成!)函数,因此编译器"知道"它从这个条件开始,并且完全可以想象流分析发现不必多次调用此函数.但事实并非如此.

是否有可能摆脱多余的call __tls_init指令,或者至少阻止编译器在时间关键的部分发出它们?

实际编译的示例情况:( - O3)

pushq   %r13
movq    %rdi, %r13
pushq   %r12
pushq   %rbp
pushq   %rbx
movq    %rsi, %rbx
subq    $88, %rsp
call    __tls_init              // always gets called
movq    (%rbx), %rdi
call    <some local function>
movq    8(%rax), %r12
subq    (%rax), %r12
movq    %rax, %rbp
sarq    $4, %r12
cmpq    $1, %r12
jbe .L6512
leaq    -2(%r12), %rax
movq    $0, (%rsp)
leaq    48(%rsp), %rbx
movq    %rax, 8(%rsp)
.L6506:
call    __tls_init              // needless and called potentially very many times!
movq    %rsp, %rsi
movq    %rsp, %rdi
addq    $8, %rbx
call    <some other local function>
movq    %rax, -8(%rbx)
leaq    80(%rsp), %rax
cmpq    %rbx, %rax
jne .L6506                      // cycle
Run Code Online (Sandbox Code Playgroud)

更新:上面的源代码过于复杂.这是一个MWE:

void external(int);

struct X {
  volatile int a;   // to prevent optimizing to a constexpr
  X() { a = 5; }    // to enforce calling a c-tor for thread_local
  void f() { external(a); } // to prevent disregarding the value of a
};

thread_local X x;

void f() {
  x.f();
  for(int j = 0; j < 10; j++)
    x.f();  // x is totally initialized now
}
Run Code Online (Sandbox Code Playgroud)

如果你看到这个在编译器浏览器最大的优化设置(分析链接到这个特殊的例子),你会发现检查的同质化现象fs:__tls_guard@tpoff0在循环的每次重复冗余把一个1后出现,即在标签.L4(假设输出将保持不变),即使__tls_init在这个超级简单的情况下内联.

虽然这个问题是关于G ++的,但CLang(参见编译器资源管理器)使这一点更加明显.

可以说外部函数调用可以覆盖此示例中的存储值.但那会有什么保证?如果是这样,它也可能不尊重调用约定.在这些方面,编译器只需要假设它会很好用.此外,我上面的主代码和单个翻译单元中没有外部函数,只是相当大(在编译器检测并删除无关测试的MWE等小例子中,显示它必须以某种方式可能).

tri*_*tan 3

我不知道是否有任何编译器选项可以消除 tls 调用,但是可以通过在函数中使用指向 TLS 对象的指针来优化您的特定代码:

void f() {
  auto ptr = &x;
  ptr->f();
  for(int j = 0; j < 10; j++)
    ptr->f(); 
}
Run Code Online (Sandbox Code Playgroud)