如何减少或消除__tls_init次来电?
How to reduce or eliminate __tls_init calls?
我正在使用依赖于 thread_local
的第三方库。这导致我的程序重复调用 __tls_init()
,即使在某些循环的每次迭代中(我没有检查所有循环),尽管 thread_local
变量已被同一个早先的另一个调用无条件初始化函数(事实上,在整个程序的开始附近)。
我的 x86_64
上 __tls_init()
中的第一条指令是
cmpb [=11=], %fs:__tls_guard@tpoff
je .L530
ret
.L530:
pushq %rbp
pushq %rbx
subq (some stack space), %rsp
movb , %fs:__tls_guard@tpoff
所以第一次为每个线程调用时,%fs:__tls_guard@tpoff
处的值设置为 1
并立即进一步调用 return。但是,这仍然意味着每次要访问 thread_local
变量时 call
的所有开销,对吧?
请注意,这是一个静态链接(实际上是生成的!)函数,因此编译器 "knows" 它以这种情况开始,完全可以想象,流分析发现没有必要调用这个功能不止一次。但事实并非如此。
是否有可能摆脱多余的 call __tls_init
指令,或者至少阻止编译器在时间关键部分发出它们?
实际编译示例情况: (-O3)
pushq %r13
movq %rdi, %r13
pushq %r12
pushq %rbp
pushq %rbx
movq %rsi, %rbx
subq , %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 , %r12
cmpq , %r12
jbe .L6512
leaq -2(%r12), %rax
movq [=12=], (%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 , %rbx
call <some other local function>
movq %rax, -8(%rbx)
leaq 80(%rsp), %rax
cmpq %rbx, %rax
jne .L6506 // cycle
Update:上面的源代码过于复杂。这是一个 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
}
如果您在 Compiler Explorer (link to this particular example) 中看到使用最大优化设置进行的分析,您会注意到在每次重复中都冗余地检查 fs:__tls_guard@tpoff
与 0
的相同现象循环 在将 1 放在那里 之后,即在标签 .L4
中(假设输出将保持不变),即使 __tls_init
内联在这个超级中简单案例.
虽然这个问题是关于 G++ 的,但 CLang (see in Compiler Explorer) 使这个问题更加明显。
在这个例子中可以说外部函数调用可以覆盖存储的值。但那能保证什么呢?如果是这样,它也可能不尊重调用约定。在这些方面,编译器只需要假设它会很好地发挥作用。此外,我上面的主要代码中没有外部函数,只有一个翻译单元,只是相当大(在像 MWE 这样的小例子中,编译器 将 检测并删除无关的测试,表明它一定是可能的)。
我不知道是否有任何编译器选项可以消除 tls 调用,但是您的特定代码可以通过在函数中使用指向 TLS 对象的指针来优化:
void f() {
auto ptr = &x;
ptr->f();
for(int j = 0; j < 10; j++)
ptr->f();
}
我正在使用依赖于 thread_local
的第三方库。这导致我的程序重复调用 __tls_init()
,即使在某些循环的每次迭代中(我没有检查所有循环),尽管 thread_local
变量已被同一个早先的另一个调用无条件初始化函数(事实上,在整个程序的开始附近)。
我的 x86_64
上 __tls_init()
中的第一条指令是
cmpb [=11=], %fs:__tls_guard@tpoff
je .L530
ret
.L530:
pushq %rbp
pushq %rbx
subq (some stack space), %rsp
movb , %fs:__tls_guard@tpoff
所以第一次为每个线程调用时,%fs:__tls_guard@tpoff
处的值设置为 1
并立即进一步调用 return。但是,这仍然意味着每次要访问 thread_local
变量时 call
的所有开销,对吧?
请注意,这是一个静态链接(实际上是生成的!)函数,因此编译器 "knows" 它以这种情况开始,完全可以想象,流分析发现没有必要调用这个功能不止一次。但事实并非如此。
是否有可能摆脱多余的 call __tls_init
指令,或者至少阻止编译器在时间关键部分发出它们?
实际编译示例情况: (-O3)
pushq %r13
movq %rdi, %r13
pushq %r12
pushq %rbp
pushq %rbx
movq %rsi, %rbx
subq , %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 , %r12
cmpq , %r12
jbe .L6512
leaq -2(%r12), %rax
movq [=12=], (%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 , %rbx
call <some other local function>
movq %rax, -8(%rbx)
leaq 80(%rsp), %rax
cmpq %rbx, %rax
jne .L6506 // cycle
Update:上面的源代码过于复杂。这是一个 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
}
如果您在 Compiler Explorer (link to this particular example) 中看到使用最大优化设置进行的分析,您会注意到在每次重复中都冗余地检查 fs:__tls_guard@tpoff
与 0
的相同现象循环 在将 1 放在那里 之后,即在标签 .L4
中(假设输出将保持不变),即使 __tls_init
内联在这个超级中简单案例.
虽然这个问题是关于 G++ 的,但 CLang (see in Compiler Explorer) 使这个问题更加明显。
在这个例子中可以说外部函数调用可以覆盖存储的值。但那能保证什么呢?如果是这样,它也可能不尊重调用约定。在这些方面,编译器只需要假设它会很好地发挥作用。此外,我上面的主要代码中没有外部函数,只有一个翻译单元,只是相当大(在像 MWE 这样的小例子中,编译器 将 检测并删除无关的测试,表明它一定是可能的)。
我不知道是否有任何编译器选项可以消除 tls 调用,但是您的特定代码可以通过在函数中使用指向 TLS 对象的指针来优化:
void f() {
auto ptr = &x;
ptr->f();
for(int j = 0; j < 10; j++)
ptr->f();
}