gpt4 book ai didi

c++ - x86上的原子性

转载 作者:行者123 更新时间:2023-12-01 23:10:34 28 4
gpt4 key购买 nike

8.1.2 Bus Locking

Intel 64 and IA-32 processors provide a LOCK# signal that is asserted automatically during certain critical memory operations to lock the system bus or equivalent link. While this output signal is asserted, requests from other processors or bus agents for control of the bus are blocked. Software can specify other occasions when the LOCK semantics are to be followed by prepending the LOCK prefix to an instruction.



它来自英特尔手册,第3卷

听起来像是对内存的原子操作将直接在内存(RAM)上执行。我很困惑,因为在分析程序集输出时看到“没什么特别的”。基本上,为 std::atomic<int> X; X.load()生成的程序集输出仅放置“额外”功能。但是,它负责适当的内存排序,而不是原子性。如果我正确理解 X.store(2)只是 mov [somewhere], $2。就这样。似乎它不会“跳过”缓存。我知道将对齐(例如ints)移动到内存是原子的。但是,我很困惑。

因此,我提出了疑问,但主要的问题是:

CPU如何在内部实现原子操作?

最佳答案

It sounds like the atomic operations on memory will be executed directly on memory (RAM).



不会,只要系统中每个可能的观察者都将操作视为原子操作,则该操作只能涉及缓存。

满足此要求非常困难 for atomic read-modify-write operations(类似于 lock add [mem], eax,尤其是地址未对齐的情况),这是CPU可能会断言LOCK#信号的时候。您仍然不会在asm中看到更多内容:硬件为 lock ed指令实现了ISA必需的语义。

尽管我怀疑现代CPU上是否存在物理外部LOCK#引脚,但内存 Controller 内置于CPU中,而不是单独的 northbridge chip中。

std::atomic<int> X; X.load() puts only "extra" mfence.



对于seq_cst加载,编译器不支持MFENCE。

我想我读过一点,旧的MSVC确实为此发出了MFENCE(也许是为了防止对无防御的NT商店进行重新订购?还是代替在商店中进行?)。但这不再了:我测试了MSVC 19.00.23026.0。在 this program that dumps its own asm in an online compile&run site的asm输出中查找foo和bar。

我们这里不需要栅栏的原因是x86内存模型 disallowsLoadStore and LoadLoad都重新排序。较早的(非seq_cst)存储区仍然可以延迟到seq_cst加载之后,因此与在 std::atomic_thread_fence(mo_seq_cst);之前使用独立的 X.load(mo_acquire);不同

If I understand properly the X.store(2) is just mov [somewhere], 2



这与您加载所需的 mfence的想法是一致的; seq_cst加载或存储中的一个或另一个需要完整的屏障,以防止禁止 StoreLoad reordering which could otherwise happen

实际上,编译器开发人员选择 廉价负载(mov)/昂贵商店(mov + mfence),因为负载更为常见。 C++11 mappings to processors

(x86内存排序模型是程序顺序加上带有存储转发( see also)的存储缓冲区。这使得 mo_acquiremo_release在asm中免费,只需要阻止编译时重新排序,并让我们选择是否将MFENCE完整 cargo 或仓库的障碍物。)

因此,seq_cst存储为 mov + mfencexchgWhy does a std::atomic store with sequential consistency use XCHG?讨论了xchg在某些CPU上的性能优势。在AMD上,MFENCE(IIRC)被证明具有额外的序列化流水线语义(用于指令执行,而不仅仅是内存顺序),可以阻止无序的exec,而在实践中的某些Intel CPU(Skylake)上,案子。

MSVC用于存储的asm与 clang's相同,使用 xchg以相同的指令执行存储+内存屏障。

原子发布或宽松存储可以只是 mov,它们之间的区别仅在于允许进行编译时重新排序的数量。

这个问题看起来像您先前的 Memory Model in C++ : sequential consistency and atomicity的第2部分,您在其中询问:

How does the CPU implement atomic operations internally?



正如您在问题中指出的那样,原子性与任何其他操作的顺序无关。 (即 memory_order_relaxed)。这仅表示该操作是作为单个不可分割的操作 hence the name发生的,而不是作为一部分可以在其他事物之前和之后发生的多个部分。

您可以“免费”获得原子性,而无需额外的硬件来对齐负载或存储多达内核,内存和I/O总线(如PCIe)之间的数据路径的大小。 ,即在各个级别的缓存之间以及在各个核心的缓存之间。在现代设计中,内存 Controller 是CPU的一部分,因此,即使是访问内存的PCIe设备也必须通过CPU的系统代理。 (这甚至使Skylake的eDRAM L4(在任何台式机CPU中都不可用:()用作内存侧缓存)(不同于Broadwell,后者将其用作L3 IIRC的牺牲品缓存)位于内存和系统中的其他所有内容之间,因此它甚至可以缓存DMA)。

Skylake system agent diagram, from IDF via ARStechnica

这意味着CPU硬件可以做任何必要的事情,以确保存储或装载相对于系统中任何其他可以观察到的东西都是原子的。如果有的话,这可能不多。 DDR内存使用足够宽的数据总线,以至于64位对齐的存储实际上确实在同一周期内通过内存总线将其通过电传输到DRAM。 (有趣的事实,但并不重要。只要一条消息足够大,像PCIe这样的串行总线协议(protocol)就不会阻止它成为原子。而且由于内存 Controller 是唯一可以直接与DRAM通讯的东西,内部执行什么无关紧要,只是它与CPU其余部分之间传输的大小无关紧要。但是无论如何, 这是“免费”部分:无需暂时阻止其他请求即可保持原子传输原子。

x86 guarantees that aligned loads and stores up to 64 bits are atomic,但访问范围更广。低功耗实现可以自由地将 vector 加载/存储分解为64位的块,就像P6从PIII到Pentium M所做的那样。

原子操作发生在缓存中

请记住,原子只是意味着所有观察者都将其视为已发生或未发生,从未部分发生过。没有要求它实际上立即到达主存储器(或者如果很快就被覆盖,则根本不存在)。 通过原子方式修改或读取L1缓存足以确保任何其他内核或DMA访问将看到对齐的存储或加载是单个原子操作。 ,如果此修改在商店执行后很长时间发生(例如,由于乱序执行而延迟到商店退役),就可以了。

像Core2之类的现代CPU到处都有128位路径,通常具有原子性的SSE 128b加载/存储,这超出了x86 ISA的保证。但是请注意有趣的异常 on a multi-socket Opteron probably due to hypertransport.这证明,原子修改L1缓存不足以为比最窄的数据路径(在这种情况下不是L1缓存和执行单元之间的路径)更宽的存储区提供原子性。

对齐很重要:跨越缓存行边界的加载或存储必须在两个单独的访问中完成。这使其成为非原子的。

AMD/Intel上的 x86 guarantees that cached accesses up to 8 bytes are atomic as long as they don't cross an 8B boundary。 (或者仅对于P6及更高版本的Intel,不要越过缓存行边界)。这意味着整个缓存行(现代CPU上为64B)在Intel上原子传输,即使它比数据路径(Haswell/Skylake上L2和L3之间的32B)宽。这种原子性在硬件上并非完全“免费”,并且可能需要一些额外的逻辑来防止负载读取仅部分传输的缓存行。尽管高速缓存行传输仅在旧版本无效后才发生,所以在进行传输时,不应从旧拷贝中读取内核。实际上,AMD可能会在较小的范围内撕裂,这可能是因为对MESI使用了不同的扩展,可以在缓存之间传输脏数据。

对于更宽的操作数,例如以原子方式将新数据写入结构的多个条目中,您需要使用一个锁来保护它,所有锁都对其进行访问。 (您可能可以将x86 lock cmpxchg16b与重试循环一起使用以进行16b原子存储。请注意 there's no way to emulate it without a mutex。)

原子读取-修改-写入会变得更困难

相关:我对 Can num++ be atomic for 'int num'?的回答对此有更详细的说明。

每个核心都有一个专用的L1缓存,该缓存与所有其他核心保持一致(使用 MOESI协议(protocol))。高速缓存行以大小从64位到256位不等的块在高速缓存和主内存级别之间传输。 (这些传输实际上可能在整个缓存行的粒度上是原子的?)

要执行原子RMW,内核可以将L1高速缓存行保持为“已修改”状态,而无需对负载和存储之间的受影响高速缓存行进行任何外部修改,系统的其余部分会将操作视为原子操作。 (因此这是原子的,因为通常的乱序执行规则要求本地线程将自己的代码视为按程序顺序运行。)

它可以通过在运行原子RMW时不处理任何高速缓存一致性消息来实现此目的(或者更复杂的版本,它可以为其他操作提供更多的并行性)。

未对齐的 lock ed操作是一个问题:我们需要其他内核才能看到对两个高速缓存行的修改是在单个原子操作中发生的。这可能需要实际存储到DRAM,并获得总线锁定。 (AMD的优化手册说,当缓存锁不足时,这就是CPU上发生的情况。)

关于c++ - x86上的原子性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38447226/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com