Clang不内联std :: atomic :: load以加载64位结构

问题描述 投票:2回答:2

请考虑以下代码,该代码使用std::atomic原子地加载64位对象。

#include <atomic>

struct A {
    int32_t x, y;
};

A f(std::atomic<A>& a) {
    return a.load(std::memory_order_relaxed);
}

使用GCC,发生了很多事情,并生成了以下代码。 (https://godbolt.org/z/zS53ZF

f(std::atomic<A>&):
        mov     rax, QWORD PTR [rdi]
        ret

这正是我所期望的,因为在这种情况下,我认为没有理由不应该将64位结构像其他任何64位字一样对待。

然而,对于Clang,故事却不同。 Clang生成以下内容。 (https://godbolt.org/z/d6uqrP

f(std::atomic<A>&):                     # @f(std::atomic<A>&)
        push    rax
        mov     rsi, rdi
        mov     rdx, rsp
        mov     edi, 8
        xor     ecx, ecx
        call    __atomic_load
        mov     rax, qword ptr [rsp]
        pop     rcx
        ret
        mov     rdi, rax
        call    __clang_call_terminate
__clang_call_terminate:                 # @__clang_call_terminate
        push    rax
        call    __cxa_begin_catch
        call    std::terminate()

这对我来说是有问题的,原因如下:

  1. 更明显的是,有更多的指令,所以我希望代码效率较低
  2. 显然,请注意,生成的代码还包括对库函数__atomic_load的调用,这意味着我的二进制文件需要与libatomic链接。这意味着我需要不同的库列表来链接,具体取决于我的代码用户使用的是GCC还是Clang。
  3. 库函数可能使用了锁,这会降低性能

我现在想到的一个重要问题是,是否有办法让Clang还将负载转换为一条指令。我们将其用作计划分发给他人的库的一部分,因此我们不能依赖于所使用的特定编译器。到目前为止,向我建议的解决方案是使用punning类型并将结构与64位int一起存储在并集内,因为Clang确实在一条指令中以原子方式正确加载了64位int。但是,我对此解决方案表示怀疑,因为尽管它似乎可以在所有主要的编译器上使用,但我已经读到它实际上是未定义的行为。如果其他人不熟悉该技巧,那么这样的代码也不是特别适合其他人阅读和理解。

总而言之,有没有一种方法可以原子方式加载64位结构:

  1. 适用于Clang和GCC,最好是大多数其他流行的编译器,
  2. 编译时只生成一条指令,
  3. 不是不确定的行为,
  4. 读者友好吗?
c++ clang atomic atomicity stdatomic
2个回答
1
投票

此Clang错过的优化仅在libstdc ++中发生;就像我们对-stdlib=libc++的期望一样,在Godbolt内联上发出叮当声。 https://godbolt.org/z/Tt8XTX

似乎对结构进行64位对齐足以握住clang。

libstdc++std::atomic模板针对自然对齐时足够小的原子类型进行此操作,但是clang ++可能只看到基础类型的对齐方式,而不是atomic<T>的类成员。 libstdc ++实现。我还没有调查有人应将此报告给clang / LLVM bugzilla。

#include <atomic>
#include <stdint.h>  // you forgot this header.

struct A {
    alignas(int64_t) int32_t x;
    int32_t y;  // this one must be separate, otherwise y would also be aligned -> 16-byte object
};

A f(std::atomic<A>& a) {
    return a.load(std::memory_order_relaxed);
}

Godbolt

# clang++ 9.0  -std=gnu++17 -O3;  g++ is the same
f(std::atomic<A>&):
        mov     rax, qword ptr [rdi]
        ret

顺便说一句,不,libatomic库函数将不使用锁;它确实知道8字节对齐的负载自然是原子的,其他使用线程将使用普通负载/存储,而不是锁。

较旧的clang至少使用call __atomic_load_8而不是通用的可变大小的clang,但这仍然是一个很大的优化遗漏。

© www.soinside.com 2019 - 2024. All rights reserved.