【问题标题】:Why this SSE2 program (integers) generate movaps (float)?为什么这个 SSE2 程序(整数)生成 movaps(浮点数)?
【发布时间】:2017-07-04 03:52:26
【问题描述】:

以下循环将一个整数矩阵转置为另一个整数矩阵。当我有趣地编译时,它会生成movaps 指令以将结果存储到输出矩阵中。为什么gcc会这样做?

数据:

int __attribute__(( aligned(16))) t[N][M]  
  , __attribute__(( aligned(16))) c_tra[N][M];

循环:

for( i=0; i<N; i+=4){
    for(j=0; j<M; j+=4){

        row0 = _mm_load_si128((__m128i *)&t[i][j]);
        row1 = _mm_load_si128((__m128i *)&t[i+1][j]);
        row2 = _mm_load_si128((__m128i *)&t[i+2][j]);
        row3 = _mm_load_si128((__m128i *)&t[i+3][j]);

        __t0 = _mm_unpacklo_epi32(row0, row1);
        __t1 = _mm_unpacklo_epi32(row2, row3);
        __t2 = _mm_unpackhi_epi32(row0, row1);
        __t3 = _mm_unpackhi_epi32(row2, row3);

        /* values back into I[0-3] */
        row0 = _mm_unpacklo_epi64(__t0, __t1);
        row1 = _mm_unpackhi_epi64(__t0, __t1);
        row2 = _mm_unpacklo_epi64(__t2, __t3);
        row3 = _mm_unpackhi_epi64(__t2, __t3);

        _mm_store_si128((__m128i *)&c_tra[j][i], row0);
        _mm_store_si128((__m128i *)&c_tra[j+1][i], row1);
        _mm_store_si128((__m128i *)&c_tra[j+2][i], row2);
        _mm_store_si128((__m128i *)&c_tra[j+3][i], row3);



    }
}

汇编生成代码:

.L39:
    lea rcx, [rsi+rdx]
    movdqa  xmm1, XMMWORD PTR [rdx]
    add rdx, 16
    add rax, 2048
    movdqa  xmm6, XMMWORD PTR [rcx+rdi]
    movdqa  xmm3, xmm1
    movdqa  xmm2, XMMWORD PTR [rcx+r9]
    punpckldq   xmm3, xmm6
    movdqa  xmm5, XMMWORD PTR [rcx+r10]
    movdqa  xmm4, xmm2
    punpckhdq   xmm1, xmm6
    punpckldq   xmm4, xmm5
    punpckhdq   xmm2, xmm5
    movdqa  xmm5, xmm3
    punpckhqdq  xmm3, xmm4
    punpcklqdq  xmm5, xmm4
    movdqa  xmm4, xmm1
    punpckhqdq  xmm1, xmm2
    punpcklqdq  xmm4, xmm2
    movaps  XMMWORD PTR [rax-2048], xmm5
    movaps  XMMWORD PTR [rax-1536], xmm3
    movaps  XMMWORD PTR [rax-1024], xmm4
    movaps  XMMWORD PTR [rax-512], xmm1
    cmp r11, rdx
    jne .L39

gcc -Wall -msse4.2 -masm="intel" -O2 -c -S skylake linuxmint

-mavx2-march=naticve 生成 VEX 编码:vmovaps

【问题讨论】:

    标签: gcc assembly x86 sse simd


    【解决方案1】:

    这些指令在功能上是相同的。 我不喜欢像我一样复制+粘贴其他人的陈述,所以很少有链接解释它:

    Difference between MOVDQA and MOVAPS x86 instructions?

    https://software.intel.com/en-us/forums/intel-isa-extensions/topic/279587

    http://masm32.com/board/index.php?topic=1138.0

    https://www.gamedev.net/blog/615/entry-2250281-demystifying-sse-move-instructions/

    短版:

    所以在大多数情况下,您应该尝试使用移动指令 对应于您将在这些上使用的操作 寄存器。但是,还有一个额外的并发症。负载和 在与整数不同的端口上执行与内存的存储和存储 和浮点单元;因此从内存中加载的指令 寄存器或从寄存器存储到内存中会经历 无论您附加到移动的数据类型如何,都具有相同的延迟。 因此 在这种情况下,movaps、movapd 和 movdqa 将具有相同的延迟 no 无论您使用什么数据。由于 movaps(和 movups)被编码在 二进制形式比其他两个少一个字节,这是有意义的 无论数据类型如何,都将其用于所有 reg-mem 移动。

    原来是 GCC 优化。

    【讨论】:

    • 其实是 Intel 和 AMD 推荐的代码生成实践。事实上,对于现代 CPU,英特尔建议您始终使用movups,因为对齐和未对齐的负载具有相同的性能——对齐的写入更重要。请参阅IntelAMD 软件优化指南。
    • @ChuckWalbourn movupsmovaps 仅具有自 Nehalem 以来的相同性能。但即使这样也是误导,因为movups 不能折叠操作,所以实际上只有vmovaps 已过时。那么你确定这是英特尔和 AMD 的建议吗?如果您的硬件支持,也许他们的意思是始终使用vmovups
    • @ChuckWalbourn 我搜索了您指向的英特尔手册,但没有找到您提到的建议。你指的是哪个部分。我还搜索了vmovaps,它在代码中出现了好几次,所以即使是英特尔仍然在使用它。
    • 英特尔 11.6.3。当您确定它已对齐时,您当然可以使用movaps,但关键是不再像以前那样对未对齐的负载造成重大的性能损失。 vmovaps 只是 movaps 使用 AVX VEX 前缀,而 AVX 优化往往侧重于对齐的内存操作。 movaps 的另一个用途是寄存器到寄存器的移动,但是。我没有看到很多 movaps 的非 VEX 版本的代码示例用于内存加载,但有很多使用 @987654339 @.
    • AMD64 手册自 2005 年以来一直没有更新,因此它仍然可以使用 movaps 而不是 movups 第 9.4 节,但较新的系列指南指出,即使在访问对齐的内存时,@987654342 @ 和 movups 具有相同的性能 (AMD Family 16h 2.5.2) 。真的这就是它的本质:过去很重要的是,您使用movaps 来对齐内存指针,使用movups 来表示未对齐的内存,但现在movups 更安全。内存对齐时速度相同,内存未对齐时不会抛出异常。它使编译器代码生成更容易一些。
    猜你喜欢
    • 2018-09-29
    • 2022-06-15
    • 2023-03-30
    • 1970-01-01
    • 1970-01-01
    • 2016-12-02
    相关资源
    最近更新 更多