未缓存 TLS 变量访问的结果

Result of TLS variable access not cached

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

我正在编写一个用于写入日志的包装器,它使用 TLS 来存储 std::stringstream 缓冲区。此代码将由共享库使用。当查看 godbolt.org 上的代码时,似乎 gcc 和 clang 都不会缓存 TLS 查找的结果(当我相信我已经设计了 [=35 时,循环重复调用'__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

这看起来确实像是 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 错误追踪器:https://bugs.llvm.org/