SSE指令:哪些CPU可以执行原子16B内存操作?

35
考虑在x86 CPU上进行单个内存访问(单个读取或单个写入,不是读取+写入)的SSE指令。该指令正在访问16字节(128位)的内存,并且所访问的内存位置与16字节对齐。
《Intel® 64体系结构内存排序白皮书》文档指出,对于“读取或写入地址对齐为8字节边界的四字(8字节)指令”,无论内存类型如何,内存操作都会执行为单个内存访问。
问题是:是否存在能够保证读取或写入16字节(128位)并对齐到16字节边界的Intel/AMD/x86 CPU能够执行单个内存访问?如果是这样,请说明是哪种特定类型的CPU(Core2 / Atom / K8 / Phenom / ...)?如果您对此问题提供了答案(是/否),请同时指定用于确定答案的方法 - PDF文档查找、暴力测试、数学证明或您用于确定答案的任何其他方法。
此问题涉及到诸如http://research.swtch.com/2010/02/off-to-races.html之类的问题。

更新:

我创建了一个简单的C语言测试程序,您可以在计算机上运行它。请编译并在您拥有的Phenom、Athlon、Bobcat、Core2、Atom、Sandy Bridge或任何支持SSE2指令集的CPU上运行它。谢谢。

// Compile with:
//   gcc -o a a.c -pthread -msse2 -std=c99 -Wall -O2
//
// Make sure you have at least two physical CPU cores or hyper-threading.

#include <pthread.h>
#include <emmintrin.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

typedef int v4si __attribute__ ((vector_size (16)));
volatile v4si x;

unsigned n1[16] __attribute__((aligned(64)));
unsigned n2[16] __attribute__((aligned(64)));

void* thread1(void *arg) {
        for (int i=0; i<100*1000*1000; i++) {
                int mask = _mm_movemask_ps((__m128)x);
                n1[mask]++;

                x = (v4si){0,0,0,0};
        }
        return NULL;
}

void* thread2(void *arg) {
        for (int i=0; i<100*1000*1000; i++) {
                int mask = _mm_movemask_ps((__m128)x);
                n2[mask]++;

                x = (v4si){-1,-1,-1,-1};
        }
        return NULL;
}

int main() {
        // Check memory alignment
        if ( (((uintptr_t)&x) & 0x0f) != 0 )
                abort();

        memset(n1, 0, sizeof(n1));
        memset(n2, 0, sizeof(n2));

        pthread_t t1, t2;
        pthread_create(&t1, NULL, thread1, NULL);
        pthread_create(&t2, NULL, thread2, NULL);
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);

        for (unsigned i=0; i<16; i++) {
                for (int j=3; j>=0; j--)
                        printf("%d", (i>>j)&1);

                printf("  %10u %10u", n1[i], n2[i]);
                if(i>0 && i<0x0f) {
                        if(n1[i] || n2[i])
                                printf("  Not a single memory access!");
                }

                printf("\n");
        }

        return 0;
}

我笔记本电脑上的CPU是Core Duo(不是Core2)。这个特定的CPU未通过测试,它实现的16字节内存读写具有8字节的粒度。输出结果如下:

0000    96905702      10512
0001           0          0
0010           0          0
0011          22      12924  Not a single memory access!
0100           0          0
0101           0          0
0110           0          0
0111           0          0
1000           0          0
1001           0          0
1010           0          0
1011           0          0
1100     3092557       1175  Not a single memory access!
1101           0          0
1110           0          0
1111        1719   99975389

真的吗?如果在Core2主板上只安装1个内存模块,你认为会发生什么?如果你恰好有一颗Core2 CPU(或其他“现代”x86-64 CPU),请尝试在你的机器上只安装1个内存模块,运行我提供的测试程序,然后请发布你的结果。谢谢。 - user811773
5
为了澄清我在评论中写的文字,它以“真的吗?……”开头。评论是对之前一位认为我的StackOverflow问题与FSB或DRAM有关的人的回应。某种程度上,我的问题与FSB和DRAM有些相关,但我的问题与FSB和DRAM的关系微不足道,并且不起重要作用。......然后那个人删除了自己的回答和评论,从历史记录中有效地将它们抹去。但是,如果你从历史上删除了自己,谁会记得你呢?没有人。 - user811773
这是一个非常有趣的讨论,谢谢!但这引发了一个问题:如果 8 字节是原子内存访问的最大大小,那么 80 位扩展浮点数是否就不是原子的了? - Jens
2
@Jens:是的,80位FPU加载可能不是原子操作。ISA不能保证(在所有实现中)它们是原子的,在实践中,它们在大多数最近的CPU上可能不是原子的。例如,在Intel Haswell上,“FLD m80”总共有4个微操作:2个ALU和2个加载端口。因此,它很可能在内部实现为64位加载和16位加载。80位FP不是性能优先级。80位FP存储需要7个微操作,吞吐量为每5个周期一个。 - Peter Cordes
7个回答

43
Intel® 64和IA-32体系结构开发者手册:Vol. 3A中,现在包含了你提到的内存排序白皮书的规格。在第8.1.1节中,它说道:
Intel486处理器(以及更新的处理器)保证以下基本内存操作始终以原子方式执行:
- 读取或写入一个字节。 - 读取或写入一个在16位边界上对齐的字。 - 读取或写入一个在32位边界上对齐的双字。Pentium处理器(以及更新的处理器)保证以下附加内存操作始终以原子方式执行: - 读取或写入一个在64位边界上对齐的四字。 - 对于适合于32位数据总线的未缓存内存位置的16位访问。
P6系列处理器(以及更新的处理器)保证以下附加内存操作始终以原子方式执行: - 对于适合于缓存行的未对齐的16位、32位和64位访问。
支持Intel® AVX(通过设置特征标志CPUID.01H:ECX.AVX[位28])的处理器保证以下指令执行的16字节内存操作始终以原子方式执行: - MOVAPD、MOVAPS和MOVDQA。 - 当使用VEX.128编码时,VMOVAPD、VMOVAPS和VMOVDQA。 - 当使用EVEX.128和k0(禁用掩码)编码时,VMOVAPD、VMOVAPS、VMOVDQA32和VMOVDQA64。
(请注意,这些指令要求其内存操作数的线性地址为16字节对齐。)
每个写操作x = (v4si){0,0,0,0}x = (v4si){-1,-1,-1,-1}可能会被编译成一个16字节的MOVAPS指令。变量x的地址是16字节对齐的。在支持AVX的Intel处理器上,这些写操作是原子的。否则,它们不是原子的。
在AMD处理器上,AMD64架构程序员手册,第7.3.2节访问原子性指出:
可缓存的、自然对齐的单个加载或存储操作,最多可以操作一个四倍字长,在任何处理器模型上都是原子性的,与此类似,完全包含在自然对齐的四倍字长内的小于四倍字长的非对齐加载或存储操作也是原子性的。非对齐的加载或存储访问通常会产生一定的延迟惩罚。关于这种延迟惩罚的与四倍字长原子性边界相关的特定模型的放宽规定,可以在特定处理器的软件优化指南中找到。 非对齐访问可能会受到其他处理器或缓存一致性设备的交错访问的影响,从而导致意外行为。必要时,可以通过使用XCHG指令或任何适当的带LOCK前缀的指令来实现非对齐访问的原子性。 报告CPUID Fn0000_0001_ECX[AVX](位28) = 1的处理器将将可缓存的、自然对齐的单个加载或存储操作的原子性从四倍字长扩展到双倍四倍字长。
换句话说,与英特尔类似,AMD处理器确保对支持AVX指令的处理器提供16字节的原子性,通过16字节的加载和存储指令实现。
在不支持AVX的Intel和AMD处理器上,可以使用带有LOCK前缀的CMPXCHG16B指令。您可以使用CPUID指令来确定您的处理器是否支持CMPXCHG16B("CX16"特性位)。
编辑:测试程序结果
(测试程序修改以将迭代次数增加10倍)
在Xeon X3450(x86-64)上:
0000 999998139 1572 0001 0 0 0010 0 0 0011 0 0 0100 0 0 0101 0 0 0110 0 0 0111 0 0 1000 0 0 1001 0 0 1010 0 0 1011 0 0 1100 0 0 1101 0 0 1110 0 0 1111 1861 999998428
在Xeon 5150(32位)上:
0000 999243100 283087 0001 0 0 0010 0 0 0011 0 0 0100 0 0 0101 0 0 0110 0 0 0111 0 0 1000 0 0 1001 0 0 1010 0 0 1011 0 0 1100 0 0 1101 0 0 1110 0 0 1111 756900 999716913
在一台Opteron 2435 (x86-64)上:
0000 999995893 1901 0001 0 0 0010 0 0 0011 0 0 0100 0 0 0101 0 0 0110 0 0 0111 0 0 1000 0 0 1001 0 0 1010 0 0 1011 0 0 1100 0 0 1101 0 0 1110 0 0 1111 4107 999998099
请注意,Intel Xeon X3450和Xeon 5150不支持AVX。Opteron 2435是一款AMD处理器(K10“伊斯坦布尔”),也不支持AVX。
这是否意味着Intel和/或AMD保证这些机器上的16字节内存访问是原子的?在我看来,不是的。在文档中没有作为保证的体系结构行为,因此无法确定在这些特定处理器上16字节内存访问是否真的是原子的,或者测试程序仅因某种原因未能触发它们。因此,依赖它是危险的。

编辑2:如何使测试程序失败

哈!我成功地使测试程序失败了。在与上述相同的Opteron 2435上,使用相同的二进制文件,但现在通过“numactl”工具运行它,指定每个线程在单独的插槽上运行,我得到了以下结果:

0000   999998634       5990
0001           0          0
0010           0          0
0011           0          0
0100           0          0
0101           0          0
0110           0          0
0111           0          0
1000           0          0
1001           0          0
1010           0          0
1011           0          0
1100           0          1  没有任何内存访问!
1101           0          0
1110           0          0
1111        1366  999994009
所以这意味着什么呢?嗯,Opteron 2435可能会,也可能不会保证在套接字内的16字节内存访问是原子的,但至少在两个套接字之间运行的HyperTransport互连上的缓存一致性协议并不提供这样的保证。
编辑3:根据“GJ”的要求,提供线程函数的汇编代码。
以下是在Opteron 2435系统上使用的GCC 4.4 x86-64版本生成的线程函数的汇编代码。

.globl thread2
        .type   thread2, @function
thread2:
.LFB537:
        .cfi_startproc
        movdqa  .LC3(%rip), %xmm1
        xorl    %eax, %eax
        .p2align 5,,24
        .p2align 3
.L11:
        movaps  x(%rip), %xmm0
        incl    %eax
        movaps  %xmm1, x(%rip)
        movmskps        %xmm0, %edx
        movslq  %edx, %rdx
        incl    n2(,%rdx,4)
        cmpl    $1000000000, %eax
        jne     .L11
        xorl    %eax, %eax
        ret
        .cfi_endproc
.LFE537:
        .size   thread2, .-thread2
        .p2align 5,,31
.globl thread1
        .type   thread1, @function
thread1:
.LFB536:
        .cfi_startproc
        pxor    %xmm1, %xmm1
        xorl    %eax, %eax
        .p2align 5,,24
        .p2align 3
.L15:
        movaps  x(%rip), %xmm0
        incl    %eax
        movaps  %xmm1, x(%rip)
        movmskps        %xmm0, %edx
        movslq  %edx, %rdx
        incl    n1(,%rdx,4)
        cmpl    $1000000000, %eax
        jne     .L15
        xorl    %eax, %eax
        ret
        .cfi_endproc

为了完整起见,.LC3是静态数据,其中包含了线程2使用的(-1, -1, -1, -1)向量。

.LC3:
        .long   -1
        .long   -1
        .long   -1
        .long   -1
        .ident  "GCC: (GNU) 4.4.4 20100726 (Red Hat 4.4.4-13)"
        .section        .note.GNU-stack,"",@progbits

请注意,这是AT&T ASM语法,而不是Windows程序员可能更熟悉的Intel语法。最后,这是使用march=native选项,使GCC更倾向于使用MOVAPS指令;但这并不重要,如果我使用march=core2选项,它将使用MOVDQA指令来存储到x,并且我仍然可以复现这些错误。

4
@GJ.:我不知道你想表达什么,但是如果处理器按照编程手册中提供的架构保证,在存储流水线中将16字节的存储指令内部实现为2个8字节的存储操作,那么另一个处理器在这两个存储操作之间 "窃取" 缓存线路是完全可能的。虽然不太可能,但是并非不可能,正如我在回答中展示的失败测试所示。 - janneb
2
@PeterCordes:我刚刚发现,在当前版本(2021年11月,尽管可能早些时候就已经引入了,但我没有关注)的英特尔手册卷3A(链接在答案中),只要CPU支持AVX,某些16字节内存操作就保证是原子性的。 - janneb
1
@janneb: 是的,我看到了Hadi的修改!这对编译器来说是一个改变游戏规则的步骤,而且也终于到了某个时候。(还有很棒的一点是,他们将其与像AVX之类的现有功能位绑定,因此新的构建可以利用一直存在的硬件功能。除了Pentium / Celeron预算CPU外;虽然将它们的SIMD执行单元的上半部分融合掉显然不会影响16字节内容的原子性,但它们仍然会失去优势。) - Peter Cordes
1
那个EDIT2太可怕了。在一个专门设计用来挑衅它的测试中,出现了一亿分之一的破坏性访问错误。祝你好运,试着基于那个错误来重现任何错误。 - TLW
1
是的,我看到你的编辑提醒了这个问答;谢谢你的维护 :) whatishappened 2022年的回答 指出AMD正在计划这样做,并且GNU libatomic已经在假设AMD保证的情况下进行了更新,但很高兴看到它得到了官方发布。 - undefined
显示剩余15条评论

6
更新:在2022年,英特尔事后记录了AVX功能位暗示对齐的128位加载/存储是原子性的,至少对于英特尔CPU而言。 AMD可以记录相同的事情,因为实际上他们支持AVX的CPU在8字节边界上避免了撕裂。请参阅@whatishappened的答案和janneb的更新答案。
在实践中,带有AVX的Pentium和Celeron版本的CPU也具有相同的原子性,但没有文档化的方法供软件检测。此外,据推测,Core 2和Nehalem以及可能一些低功耗的Silvermont系列芯片直到Alder Lake E-cores才具备AVX功能。

所以最后我们可以以相对便宜的方式在AVX CPU上进行__int128的加载/存储,并且有良好的文档支持。(因此,C++ std::atomicis_lock_free() 在某些机器上可以返回true。 但是不会作为编译时的常量 is_always_lock_free,除非架构选项使生成需要AVX的二进制文件。GCC之前使用 lock cmpxchg16b 实现加载/存储,但在GCC7中更改了该行为,因为它没有具备你预期的正确支持的读取端缩放能力。)


以下是旧的部分更新的答案

Erik Rigtorp对最近的Intel和AMD CPU进行了一些实验测试,以寻找撕裂现象。结果可在https://rigtorp.se/isatomic/找到。请记住,这种行为没有任何文档或保证(除了128位或非AVX CPU之外),而且我不确定使用此类CPU的自定义多插槽机器是否可能具有比他所测试的机器更少的原子性。但对于当前的x86 CPU(不包括K10),对齐加载/存储的SIMD原子性仅随缓存和L1d缓存之间的数据路径宽度进行扩展。



x86指令集只对最多8字节的操作保证原子性,因此实现可以自由地按照Pentium III / Pentium M / Core Duo这样的方式支持SSE / AVX:内部数据以64位的方式处理。128位存储被分为两个64位存储进行。Yonah微架构(Core Duo)中到/从缓存的数据路径仅为64位宽。(来源:Agner Fog的微架构文档)。

更近期的实现在内部具有更宽的数据路径,并将128位指令作为单个操作处理。Core 2 Duo(conroe/merom)是第一个具有128位数据路径的Intel P6后代微架构。(关于P4我不太清楚,但幸运的是,它已经过时了,完全无关紧要。)

这就是为什么原帖作者发现在Intel Core Duo(Yonah)上128位操作不是原子的,而其他用户发现它们在后来的Intel设计上是原子的,从Core 2(Merom)开始。

这篇关于Merom vs. Yonah的Realworldtech文章中的图表显示了Merom(和P4)中ALU和L1数据缓存之间的128位路径,而低功耗的Yonah则具有64位数据路径。所有三种设计中L1和L2缓存之间的数据路径为256位。

数据路径宽度的下一个跃升是由Intel的Haswell引入的,具有256位(32字节)AVX/AVX2加载/存储,以及L1和L2缓存之间的64字节路径。我预计在Haswell、Broadwell和Skylake中,256位的加载/存储是原子性的,但我没有一个来进行测试。

Skylake-AVX512具有512位的数据路径,因此它们在读写L1d缓存时也是自然原子性的。环形总线(客户端芯片)以32字节块传输,但Intel保证在32字节的一半之间不会发生撕裂,因为他们保证了8字节的加载/存储在任何对齐错误的情况下都是原子性的,只要它不跨越缓存行边界。

Zen 4将512位操作处理为两个部分,因此可能不支持512位原子性。
正如janneb在他出色的实验回答中指出的那样,在多核系统中,套接字之间的高速缓存一致性协议可能比在共享末级缓存CPU内部获得的要窄。对于宽加载/存储,没有原子性的架构要求,因此设计人员可以在套接字内部使其具有原子性,但如果方便的话,在套接字之间不具备原子性。我不知道AMD的Bulldozer系列或Intel的套接字之间逻辑数据路径有多宽。(我说“逻辑”,因为即使数据以较小的块传输,直到完全接收到才会修改缓存行。)
找到关于AMD CPU的类似文章应该能够得出关于128位操作是否原子的合理结论。仅仅检查指令表是有一些帮助的:
K8将movaps reg, [mem]解码为2个微操作,而K10和bulldozer系列将其解码为1个微操作。AMD的低功耗bobcat将其解码为2个操作,而jaguar将128位movaps解码为1个微操作。(它支持与bulldozer系列CPU相似的AVX1:256位指令(甚至ALU操作)被分成两个128位操作。Intel SnB只分割256位加载/存储,同时具有全宽度的ALU。)
janneb的Opteron 2435是6核Istanbul CPU,属于K10系列,因此在单个插槽内,这个单一微操作->原子结论似乎是准确的。
Intel Silvermont使用单个uop进行128位加载/存储,并且每个时钟周期的吞吐量为一个。这与整数加载/存储相同,所以很可能是原子的。

我看到了你的评论,提到一些保证是事后记录的。是否愿意更新答案? - Alex Guteniev
@AlexGuteniev:Janneb已经在这个问题上更新了相关信息的答案。但是我想我应该纠正这个答案关于缺乏供应商文档的说法;感谢你指出这一点。已更新。 - Peter Cordes

5
在第3.9.1节中,"AMD Architecture Programmer's Manual Volume 1: Application Programming"表示:“CMPXCHG16B可用于64位模式下执行16字节原子访问(带有某些对齐限制)。”
然而,关于SSE指令却没有这样的评论。实际上,4.8.3中有一个注释,即LOCK前缀“在使用128位媒体指令时会导致无效操作码异常”。因此,对我来说似乎非常确定,AMD处理器不能保证对于SSE指令进行原子128位访问,唯一的方法是使用CMPXCHG16B
"Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming Guide, Part 1"在8.1.1中表示:“访问大于四个字的数据的x87指令或SSE指令可能使用多个内存访问来实现。”这非常明确,ISA不保证128位SSE指令是原子的。Intel文档的Volume 2A表示CMPXCHG16B:“可以使用LOCK前缀来执行原子操作。”
此外,CPU制造商并没有发布有关特定CPU型号保证原子128位SSE操作的书面保证。

CMPXCHG16B指令的含义是:如果MEM [ADDR] == X,则MEM [ADDR]:= Y。这意味着您需要先知道存储在ADDR处内存中的值X,然后再写入Y。我的问题假设存储在ADDR处的128位数据类型使用了所有128位 - 因此可能存储任何(1 << 128)个可能的值。因此,无法确定MEM [ADDR]是否等于X。(旁注:有一种通用方法可以使用多个字节(例如:24字节)对16字节值进行编码,以便可以安全地读取/写入16字节。但那是另一个问题。) - user811773
5
你可以使用 CMPXCHG16B 读取一个值。首先将任何值加载到 RDX:RAX 中,并将相同的值加载到 RCX:RBX 中。所有寄存器都设为零即可。然后执行 CMPXCHG16B [addr]。如果值匹配,则存储回相同的值。如果它们不匹配,则会更新 RDX:RAX 为实际的值。无论哪种方式,RDX:RAX 包含原始存储的值,而内存保持不变。 - Anthony Williams
关于你的句子“This is pretty conclusive that 128-bit SSE instructions are never atomic”:没有证据支持特定CPU上这个句子的说法。到目前为止,对我的问题的回答都支持这样一种说法:在Core2 Quad Q6600、Core2 Duo P8400、Pentium4超线程、Xeon X3450、Xeon 5150和1插槽Opteron 2435上,内存访问始终是原子性的。也许测试程序应该被修改,以便数据更经常地通过内存芯片,或者测试程序应该在机器上运行/执行其他任务的同时运行更长时间(1小时)。 - user811773
1
抱歉,我的意思是这些操作并不保证原子性。特定的CPU可能会使它们具有原子性,在特定的测试条件下它们可能看起来是原子性的,但并没有保证。 - Anthony Williams
我认为实际上一些CPU设计可能确实具有原子128b的加载/存储,因为它们的宽数据路径从未将操作分成单独的部分。文档从未提到过这一点,也没有CPUID位,因为他们不希望人们编写依赖于它的代码。(未来的低功耗CPU始终可以使用更窄的数据路径实现SIMD。)但是,仅因为没有Intel或AMD手册中保证书面化并不意味着没有保证,例如对于Sandybridge。而且很可能Haswell上的256b是原子性的。请参见我的答案。进行了编辑,因此我不必-1。 - Peter Cordes
@AnthonyWilliams,使用CMPXCHG16B读取值的问题在于,在只读内存上使用CMPXCHG16B会导致段错误。请参见https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878和https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94649。 - zwhconst

3
实际上,在Intel Architecture Manual Vol 3A中有一个警告。8.1.1节(2011年5月)下保证原子操作的部分写着:
“访问大于四字的数据的x87指令或SSE指令可能会使用多个内存访问。如果这样的指令存储到内存中,一些访问可能会完成(写入内存),而另一些访问由于架构原因(例如由于标记为“不存在”的页表条目)导致操作失败。在这种情况下,即使整个指令引起故障,已完成的访问的影响可能对软件可见。如果TLB失效被延迟执行(参见4.10.4.4节),即使所有访问是对同一页进行的,也可能发生此类页面错误。”
因此,即使底层架构使用单个内存访问,SSE指令也不能保证是原子性的(这就是引入内存屏障的原因之一)。
再加上在Intel Optimization Manual中第13.3节(2011年4月)所述:
“AVX和FMA指令不引入任何新的保证原子性的内存操作。”
以及SIMD的所有加载或存储操作均未保证原子性,我们可以得出结论:Intel目前不支持任何形式的原子SIMD。
作为额外的内容,如果在使用类似于movdqu这样的指令允许非对齐访问时,内存被分割成缓存行或页面边界,则以下处理器不会执行原子访问(无论对齐还是非对齐),但后续处理器会执行(再次引用自Intel Architecture Manual):
“Intel Core 2 Duo、Intel® Atom™、Intel Core Duo、Pentium M、Pentium 4、Intel Xeon、P6家族、Pentium和Intel486处理器。其中Intel Core 2 Duo、Intel Atom、Intel Core Duo、Pentium M、Pentium 4、Intel Xeon和P6家族处理器。”

问题明确说明所有16字节的内存访问都对齐到16字节。因此,它们中的任何一个在构造时都不会跨越缓存行或页面边界。 - user811773
关于英特尔架构手册中的“可以使用多个内存访问实现”的措辞:我的问题是是否存在具体的物理CPU,其实现方式始终是内存访问是原子性的。 - user811773
@Necrolis:是的,而movdqa并不执行任何SIMD操作,它只是寄存器和内存之间的移动。 - GJ.
@Atom:这个词汇基本上意味着:“在我们当前的产品范围内没有后续的,但是我们可以在未来添加它”,也就是说,目前没有官方支持此功能的处理器(来自英特尔),而在这个级别上的测试是不可靠的非官方证明。缓存分割/页面边界等内容只是一些额外的杂项信息,主要用于像movdqu这样的东西。 - Necrolis
@GJ:它仍然是一个SSE2指令,因此属于流SIMD的范畴(特别是因为加载和存储是多个打包值)。顺便说一句,它也适用于寄存器到寄存器的移动:P - Necrolis
@Necrolis:是的,这对于多个打包值的加载和存储是正确的。但对于单个16字节读取或16字节写入则不然。 - GJ.

2
似乎AMD将在下一版手册中规定,在支持AVX的x86处理器上,对齐的16字节加载和存储是原子性的。(来源)抱歉回复晚了!我们将在下一版更新AMD APM手册。对于所有AMD架构,支持AVX的处理器将缓存、自然对齐的单个加载或存储的原子性从四个字节扩展到双倍四个字节。这意味着所有128位指令,甚至是*MovDQU指令,如果它们最终被自然地对齐,都是原子性的。我们能否将此补丁扩展到AMD处理器上?如果不行,我将计划提交第一阶段的补丁!
通过这个补丁,libatomic 的实现中在使用 __atomic_load_16 和 __atomic_store_16 时不仅会在支持 AVX 的英特尔处理器上使用 vmovdqa,而且也会在支持 AVX 的 AMD 处理器上使用。该补丁已经 合并到主分支 中。

-1

编辑: 在过去的两天里,我对我的三台电脑进行了多次测试,但我没有复制任何内存错误,所以我不能更加准确地说出什么。也许这个内存错误也取决于操作系统。

编辑: 我正在使用Delphi编程而不是C语言,但我应该能够理解C语言。因此,我已经翻译了代码,以下是线程过程,其中主要部分是用汇编语言编写的:

procedure TThread1.Execute;
var
  n             :cardinal;
const
  ConstAll0     :array[0..3] of integer =(0,0,0,0);
begin
  for n := 0 to 100000000 do
    asm
      movdqa    xmm0, dqword [x]
      movmskps  eax, xmm0
      inc       dword ptr[n1 + eax *4]
      movdqu    xmm0, dqword [ConstAll0]
      movdqa    dqword [x], xmm0
    end;
end;

{ TThread2 }

procedure TThread2.Execute;
var
  n             :cardinal;
const
  ConstAll1     :array[0..3] of integer =(-1,-1,-1,-1);
begin
  for n := 0 to 100000000 do
    asm
      movdqa    xmm0, dqword [x]
      movmskps  eax, xmm0
      inc       dword ptr[n2 + eax *4]
      movdqu    xmm0, dqword [ConstAll1]
      movdqa    dqword [x], xmm0
    end;
end;

结果:在我的四核PC和双核PC上都没有错误,正如预期的那样!

  1. 搭载Intel Pentium4 CPU的PC
  2. 搭载Intel Core2 Quad CPU Q6600的PC
  3. 搭载Intel Core2 Duo CPU P8400的PC

您能展示一下调试器如何查看您的线程过程代码吗?拜托了...


@janneb:好的,我已经将代码的主线程部分翻译成助记符,并在两台电脑上进行了测试。结果:没有错误! - GJ.
@GJ:谢谢。您能否更新您的答案,包括您测试过的CPU类型 - 就像“janneb”所做的那样。 - user811773
@GJ:关于你测试的Pentium4 CPU:它是否具有多个核心和/或超线程技术? - user811773
@Atom:第一个具有超线程技术。其他两个分别在4核和2核下工作。操作系统分别为前两个是Win XP,最后一个是Win Vista。 - GJ.
@GJ:我将janneb的答案标记为正确答案。赏金归于你。 - user811773
显示剩余2条评论

-1
到目前为止,已经发布了很多答案,因此已经有很多信息可用(作为副作用也有很多混淆)。我想引用英特尔手册中关于硬件保证原子操作的事实...
在英特尔最新的Nehalem和Sandy Bridge系列处理器中,读取或写入对齐到64位边界的四字节是有保证的。
即使是未对齐的2、4或8字节的读取或写入,只要它们是缓存内存并适合于缓存行,就保证是原子性的。
话虽如此,这个问题中发布的测试在基于Sandy Bridge的英特尔i5处理器上通过了。

1
这个问题特别涉及到16字节的读写,而quadword是8字节。无论如何,感谢您运行测试程序。 - user811773

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接