x64 CPU 上的原子 16 字节读取

Posted

技术标签:

【中文标题】x64 CPU 上的原子 16 字节读取【英文标题】:Atomic 16 byte read on x64 CPUs 【发布时间】:2012-03-15 19:15:20 【问题描述】:

我需要以原子方式读/写 16 个字节。我只使用 cmpxchg16 进行写作,它适用于所有 x64 处理器,但我认为只有一个不起眼的 AMD 处理器。

现在的问题是对齐的 16 字节值,只有使用 cmpxchg16 进行修改(它就像一个完整的内存屏障)是否有可能读取一半旧数据和一半新数据的 16 字节位置?

只要我使用 SSE 指令读取(因此线程不能在读取过程中被中断),我认为读取是不可能的(即使在多处理器 numa 系统中)看到不一致的数据。我认为它必须是原子的。

我假设当 cmpxchg16 被执行时,它会原子地修改 16 个字节,而不是通过写入两个 8 字节块来让其他线程在两者之间进行读取(老实说,我不明白它是如何如果它不是原子的就可以工作。)

我说的对吗?如果我错了,有没有办法在不求助于锁定的情况下进行原子 16 字节读取?

注意:有一个couple similar questions here,但它们不处理仅使用 cmpxchg16 完成写入的情况,所以我觉得这是一个单独的、未回答的问题。

编辑:其实我认为我的推理是错误的。一条 SSE 加载指令可以作为两次 64 位读取执行,并且 cmpxchg16 可能会在两次读取之间由另一个处理器执行。

【问题讨论】:

在链接的问题中已经回答了 16 字节 SSE 读取可以通过多个内存访问来实现,即它们不是原子的。使用 CMPXCHG16B 以原子方式完成写入并没有什么不同。读取也必须是原子的,否则您可能会看到不一致的数据。 AFAIK 您唯一的选择是使用 CMPXCHG16B 阅读。 是的,我错误地认为我只需要在读取之间停止线程被中断,但实际的总线操作本身仍然可以交错。 在读取上使用 cmpxchg16b 会使读取速度减慢到无法接受的程度。但是通过多使用 25% 的内存,我可以使用像 Dmitry Vyukov 的 hashmap 这样的 seqlock 风格的方法:1024cores.net/home/downloads 【参考方案1】:
typedef struct

  unsigned __int128 value;
 __attribute__ ((aligned (16))) atomic_uint128;

unsigned __int128
atomic_read_uint128 (atomic_uint128 *src)

  unsigned __int128 result;
  asm volatile ("xor %%rax, %%rax;"
                "xor %%rbx, %%rbx;"
                "xor %%rcx, %%rcx;"
                "xor %%rdx, %%rdx;"
                "lock cmpxchg16b %1" : "=A"(result) : "m"(*src) : "rbx", "rcx");
  return result;

这应该可以解决问题。 typedef 确保正确对齐。 cmpxchg16b 需要在 16 字节边界上对齐数据。

cmpxchg16b 将测试*src 是否包含零,如果是则写入零(nop)。在任何一种情况下,正确的值都会在 RAX:RDX 之后出现。

上面的代码计算结果很简单

push   %rbx
xor    %rax,%rax
xor    %rbx,%rbx
xor    %rcx,%rcx
xor    %rdx,%rdx
lock cmpxchg16b (%rdi)
pop    %rbx
retq

【讨论】:

是的,我认为这一定是这样做的。现在我突然想到,一个简单的 SSE 负载可以拆分为两个 64 位读取,并且 cmpxchg16 可能会在读取之间发生。【参考方案2】:

根据引用 http://siyobik.info/main/reference/instruction/CMPXCHG8B%2FCMPXCHG16B CMPXCHG16 默认不是原子的,但可以通过使用 LOCK http://siyobik.info/main/reference/instruction/LOCK 使其成为原子

这意味着默认情况下,可以在读取和写入阶段更改数据。锁定使读取和写入都是原子的。

【讨论】:

"请注意,CMPXCHG16B 要求目标(内存)操作数是 16 字节对齐的。" 对不起,是的,我的意思是 cmpxchg16 带有锁定前缀。但是 lock 不能与 SSE 指令一起使用。

以上是关于x64 CPU 上的原子 16 字节读取的主要内容,如果未能解决你的问题,请参考以下文章

在 Java 中原子读取然后写入部分 ByteBuffer

原子操作的实现原理

17.TLB

Linux 上的管道读取是原子的吗(多个写入器,一个读取器)?

在 x64 上再次读取之前在未缓存的地址写入完整的缓存行

总线锁定与一致性缓存