在这种*非常*特定的情况下执行原子读取的最快方法?

问题描述 投票:0回答:1

背景

事实证明所有(?)编译器都将std::atomic::load(std::memory_order_relaxed)

视为
易失性负载(通过__iso_volatile_load64
等)。
他们根本不优化或重新排序。即使丢弃加载的值仍然会生成加载指令,因为编译器将其视为可能产生副作用。

因此,松弛载荷不是最优的。话虽如此...


问题(x86)

假设

p

 指向共享内存中
单调递增的 8 字节计数器,该计数器仅写入到外部我的进程。我的程序只从这个地址读取。

我想以这样的方式读取这个计数器:

  1. 负载是

    原子(无撕裂)

  2. 此柜台保留订单(因此x = *p; y = *p;

    意味着
    x <= y

  3. 负载

    被视为不透明/优化障碍(上面#2除外)

特别是,这里的目的是编译器在正常内存访问时执行尽可能多的优化,例如:无用的加载(如

(void)*p;

)被丢弃,
other指令被自由地重新排序这个内存访问等等

除了使用易失性负载之外,还有什么方法可以在 MSVC 或 Clang 上实现此目的吗?

(特定于实现的黑客/内在函数/等。

都可以,只要这些特定的实现永远不会将其视为未定义的行为,因此不存在错误代码生成的风险。)

c++ visual-c++ x86 clang atomic
1个回答
0
投票

std::atomic<uint64_t> *p

std::atomic_ref<>
std::memory_order_relaxed
 可以满足您的大部分需求,除了 
公共子表达式消除 (CSE) 之外。在未来的编译器中,您甚至可能会得到有限的负载,或者至少优化掉未使用的负载。纸面上的 ISO C++ 保证勉强足以满足您的用例。

我不知道有什么比这更弱但仍然安全的东西。让它变得简单(非原子/非易失性)并且不会给你读读连贯性。即使您在源代码中写入int x = *p;

,某些(也许不是全部)后来使用的
x
实际上可能会从
*p
重新加载。请参阅 LWN 上的
谁害怕一个糟糕的优化编译器? 的“发明的负载”部分。稍后使用 x
 时可能会发生这种情况,但并非全部,从而使变量值发生变化。或者对于 
x
 但不是 
y
,允许违反 
x<=y

也许您使用 GNU C 内联汇编(如

int x = *p; asm("" ::: "memory");

)来告诉编译器 
*p
 可能已更改。或者可能是像 
asm("" : "+g"(*p))
 这样对优化伤害较小的东西,告诉它只忘记 
*p
 的值,而不成为所有内存重新排序的编译器障碍。但这仍然会阻止多次加载的 CSE,因为您仍然手动告诉编译器在哪里忘记事情。

此外,假设它不是

x = *p

volatile
,则可能会非原子地执行 
atomic
,具体取决于周围的代码; 
64 位计算机上的哪些类型在 gnu C 和 gnu C++ 中自然是原子的? -- 意味着它们具有原子读取和原子写入 显示了 AArch64 上的 64 位存储示例,GCC 选择使用对两半具有相同值的 stp
 进行编译,这不能保证原子性直到 ARMv8.4 之类的。因此,使用非原子类型并依赖内存屏障是两个世界中最糟糕的,并且“不能”通过任何特定于编译器的保证来保证工作;它仍然是 MSVC 和 GNU C++ 中的数据竞赛 UB

std::atomic<>

relaxed
 满足您的正确性要求

无锁
    std::atomic
  1. 加载始终编译为硬件中原子的东西,并且

    atomic<uint64_t>

     在所有主流 x86 编译器上应该是无锁的,即使在 32 位模式下也是如此。 (对 MSVC 不是 100% 确定,但 GCC 和 Clang 知道如何使用 SSE2 
    movq
     在 32 位模式下进行 8 字节原子加载。)

    甚至
  2. relaxed
  3. 原子也具有读读一致性(

    [intro.races]/16

    ):后续读取将在修改顺序中看到相同或较晚的值。这可以防止编译时重新排序,即使在非 x86 ISA 上,一致的缓存 + 硬件保证也可以免费实现这一点(无需任何额外的屏障指令)。

    编译器可以并且确实围绕原子加载/存储对其他变量上的其他内存操作进行重新排序。
  4. 对于 GCC/clang 的

    relaxed

     也是如此(以及带有 
    volatile

    的 MSVC,以确保即使在针对 x86 进行编译时,也不会将其视为

    /volatile:iso

    /
    acquire
    )。但是 
    release
    std::atomic
     可移植地准确表达您想要的语义,因此未来的编译器可能会更好地优化。并且 
    relaxed
     操作无法在编译时与其他 
    volatile
     操作一起重新排序,无论位于哪个位置,这与原子 
    volatile
     不同,它可以。
    
        

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