为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

桃花长相依 2019-10-11 14:37:15
考虑x86 CPU上的单个内存访问(单个读取或单个写入,而不是读写)。该指令正在访问16个字节(128位)的存储器,并且所访问的存储器位置与16个字节对齐。文档“英特尔®64架构内存订购白皮书”指出,对于“读取或写入地址在8字节边界上对齐的四字(8字节)的指令”,内存操作似乎作为单个内存访问执行内存类型。问题:是否存在Intel / AMD / etc x86 CPU,它们保证对16个字节边界对齐的16个字节(128位)的读写操作作为单个内存访问执行?是的,它是哪种特定类型的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);
查看完整描述

3 回答

?
守候你守候我

TA贡献1802条经验 获得超10个赞

在《英特尔®64和IA-32架构开发人员手册》中。3A如今包含您提到的内存订购白皮书的规格,在第8.2.3.1节中说,正如您自己指出的那样,


Intel-64内存排序模型可确保以下各项 

内存访问指令,似乎要执行组成内存操作 

作为单个内存访问:


•读取或写入单个字节的指令。

•读取或写入地址(2字节)对齐的字(2个字节)的指令

字节边界。

•读取或写入地址对齐的双字(4个字节)的指令

在4个字节的边界上。

•读取或写入地址对齐在其上的四字(8字节)的指令

8字节边界。


任何锁定的指令(XCHG指令或其他读-修改-写

 带有LOCK前缀的指令)似乎作为不可分割的和 

不间断的装载顺序,然后是存储顺序,与对齐方式无关。

现在,由于上面的列表不包含双四字(16字节)的相同语言,因此该体系结构不保证访问16字节内存的指令是原子的。


话虽如此,最后一段确实暗示了一条出路,即带有LOCK前缀的CMPXCHG16B指令。您可以使用CPUID指令确定处理器是否支持CMPXCHG16B(“ CX16”功能位)。


在相应的AMD文档《AMD64技术AMD64体系结构程序员手册》第2卷:系统编程中,我找不到相似的清晰语言。


编辑:测试程序结果


(修改了测试程序,使#迭代次数增加了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和/或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”请求,用于线程功能的ASM。


这是为Opteron 2435系统上使用的GCC 4.4 x86-64版本的线程函数生成的asm:



.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是包含thread2使用的(-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语法。最后,这是进行曲=本机,这使GCC更喜欢MOVAPS;但这没关系,如果我使用march = core2,它将使用MOVDQA来存储到x,并且仍然可以重现故障。


查看完整回答
反对 回复 2019-10-11
?
慕妹3242003

TA贡献1824条经验 获得超6个赞

的“AMD架构程序员手册卷1:应用程序编程”表示在节3.9.1:“ CMPXCHG16B可被用来执行在64位模式的16字节的原子访问(与某些对齐限制)”。


但是,没有关于SSE指令的评论。实际上,在4.8.3中有一条注释,即LOCK前缀“与128位媒体指令一起使用时会导致无效操作码异常”。因此,在我看来,AMD处理器不能保证对SSE指令进行原子128位访问是完全结论性的,并且进行原子128位访问的唯一方法是使用CMPXCHG16B。


“ 英特尔64和IA-32体系结构软件开发人员手册第3A卷:系统编程指南,第1部分 ”在8.1.1中说:“可以使用多次内存访问来实现访问大于四字的数据的x87指令或SSE指令。 ” 这是非常确定的,ISA不能保证128位SSE指令是原子的。 英特尔文档的第2A卷说CMPXCHG16B:“此指令可以与LOCK前缀一起使用,以允许原子执行该指令。”


此外,在这种情况下,CPU制造商尚未发布针对特定CPU型号的128b SSE原子操作的书面保证。


查看完整回答
反对 回复 2019-10-11
  • 3 回答
  • 0 关注
  • 913 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信