未缓存 TLS 变量访问的结果

Posted

技术标签:

【中文标题】未缓存 TLS 变量访问的结果【英文标题】:Result of TLS variable access not cached 【发布时间】:2017-12-11 13:49:27 【问题描述】:

编辑:看来这确实是一个编译器错误:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82803

我正在编写一个用于写入日志的包装器,它使用 TLS 来存储 std::stringstream 缓冲区。此代码将由共享库使用。查看 godbolt.org 上的代码时,似乎 gcc 和 clang 都不会缓存 TLS 查找的结果(当我相信我已经以一种应该允许的方式设计了我的类时,循环重复调用了 '__tls_get_addr()'。

#include <sstream>

class LogStream

public:
    LogStream()
    :   m_buffer(getBuffer())
    
    

    LogStream(std::stringstream& buffer)
    :   m_buffer(buffer)
    
    

    static std::stringstream& getBuffer()
    
        thread_local std::stringstream buffer;
        return buffer;
    

    template <typename T>
    inline LogStream& operator<<(const T& t)
    
        m_buffer << t;
        return *this;
    

private:
    std::stringstream& m_buffer;
;


int main()

    LogStream log;

    for (int i = 0; i < 12345678; ++i)
    
        log << i;
    

查看汇编代码输出 gcc 和 clang 生成非常相似的输出:

clang 5.0.0:

xor ebx, ebx
.LBB0_3: # =>This Inner Loop Header: Depth=1
data16
lea rdi, [rip + LogStream::getBuffer[abi:cxx11]()::buffer[abi:cxx11]@TLSGD]
data16
data16
rex64
call __tls_get_addr@PLT    // Called on every loop iteration.
lea rdi, [rax + 16]
mov esi, ebx
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)@PLT
inc ebx
cmp ebx, 12345678
jne .LBB0_3

gcc 7.2:

xor ebx, ebx
.L3:
lea rdi, guard variable for LogStream::getBuffer[abi:cxx11]()::buffer@tlsld[rip]
call __tls_get_addr@PLT   // Called on every loop iteration.
mov esi, ebx
add ebx, 1
lea rdi, LogStream::getBuffer[abi:cxx11]()::buffer@dtpoff[rax+16]
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)@PLT
cmp ebx, 12345678
jne .L3

如何让两个编译器相信不需要重复进行查找?

编译器选项:-std=c++11 -O3 -fPIC

Godbolt link

【问题讨论】:

强制 getbuffer 不内联或动态分配 LogStream 似乎对我有用......不知道为什么,对我来说内联逻辑似乎很糟糕...... 不指定-fPIC也不会生成奇怪的代码。 @SebastianRedl 是的,独立应用的生成还可以 确实,见Bug 82803 【参考方案1】:

这看起来确实像是 Clang 和 GCC 中的优化错误。

这就是我认为会发生的事情。 (我可能完全关闭了。)编译器将所有内容完全内联到这段代码:

int main()

    // pseudo-access
    std::stringstream& m_buffer = LogStream::getBuffer::buffer;
    for (int i = 0; i < 12345678; ++i)
    
        m_buffer << i;
    

然后,没有意识到在-fPIC 下访问局部线程非常昂贵,它决定不需要对全局的临时引用并内联它:

int main()

    for (int i = 0; i < 12345678; ++i)
    
        // pseudo-access
        LogStream::getBuffer::buffer << i;
    

无论实际发生什么,这显然是对您编写的代码的悲观化。您应该将此作为错误报告给 GCC 和 Clang。

GCC 错误跟踪器:https://gcc.gnu.org/bugzilla/ Clang bugtracker:https://bugs.llvm.org/

【讨论】:

看来gcc至少已经被报道过了:gcc.gnu.org/bugzilla/show_bug.cgi?id=82803

以上是关于未缓存 TLS 变量访问的结果的主要内容,如果未能解决你的问题,请参考以下文章

线程局部存储的Win32实现

d从共享环境访问tls

后台代理 ParseUser.CurrentUser 未缓存/不可访问 Windows Phone

是啥导致我的代码中的缓存未命中?

分布式缓存中的访问路径变量

Envoy源码分析之ThreadLocal