assembly - 为什么 SIMD 比标量对应物慢

标签 assembly x86 sse simd

这是另一个 SSE 比普通代码慢!为什么? 类型的问题。
我知道有一堆类似的问题,但他们似乎不符合我的情况。

我正在尝试实现 Miller-Rabin primality testMontgomery Modular Multiplication用于快速模运算。
我尝试以标量和 SIMD 方式实现它,结果证明 SIMD 版本慢了大约 10%。
如果有人想知道的话,[esp+16] 或 [esp+12] 指向 N 的模逆。

我真的很困惑,一个据称是 1 Latency 1c Throughput 1uops 的指令 psrldq 需要超过 3 Latency 0.5c Throughput 1uops pmuludq

下面是在 Ryzen 5 3600 上运行的 visual studio 上的代码和运行时分析。

如有任何关于如何改进 SIMD 代码和/或为什么它比标量代码慢的想法,我们将不胜感激。

附言似乎运行时分析由于某种原因被一条指令关闭了

编辑 1:图片上的评论有误,我在下面附上了修复后的版本:

    ;------------------------------------------
    ; Calculate base^d mod x
    ;
    ; eax = 1
    ; esi = x
    ; edi = bases[eax]
    ; ebp = d
    ; while d do
    ;     if d & 1 then eax = (eax * edi) mod x
    ;     edi = (edi*edi) mod x
    ;     d >>= 1
    ; end
    ;------------------------------------------

标量代码:

LOOP_MODEXP:
    push eax

    test ebp, 1
    jz @F

    mul edi
    mov ecx, edx
    imul eax, DWORD PTR [esp+16]
    mul esi
    xor ebx, ebx
    sub ecx, edx
    cmovs ebx, esi
    add ecx, ebx
    mov DWORD PTR [esp], ecx
@@:
    mov edx, edi
    mulx ecx, edx, edi
    imul edx, DWORD PTR [esp+16]
    mulx eax, ebx, esi
    xor ebx, ebx
    sub ecx, eax
    cmovs ebx, esi
    add ecx, ebx
    mov edi, ecx

    pop eax

    shr ebp, 1
    jnz LOOP_MODEXP

Scalar run time analysis

SIMD代码

    movd xmm2, DWORD PTR [esp+12]
    movd xmm3, esi
    pshufd xmm2, xmm2, 0
    pshufd xmm3, xmm3, 0
    
    movd xmm1, edi

    pshufd xmm1, xmm1, 0
    movdqa xmm0, xmm1

    pinsrd xmm0, eax, 2

LOOP_MODEXP:
    movdqa xmm4, xmm0
    pmuludq xmm0, xmm1
    movdqa xmm1, xmm0
    pmuludq xmm0, xmm2
    pmuludq xmm0, xmm3
    psubd xmm1, xmm0
    
    psrldq xmm1, 4
    pxor xmm0, xmm0
    pcmpgtd xmm0, xmm1
    blendvps xmm0, xmm3, xmm0
    paddd xmm0, xmm1

    movddup xmm1, xmm0

    test ebp, 1
    jnz @F
    blendps xmm0, xmm4, 4

@@:
    shr ebp, 1
    jnz LOOP_MODEXP

    pextrd eax, xmm0, 2

SIMD run time analysis

最佳答案

  1. 您的 SIMD 代码浪费时间错误预测测试 ebp, 1/jnz 分支。 SSE 中没有条件移动指令,但您仍然可以使用更多指令优化测试 + 分支,如下所示:
mov      ebx, ebp
and      ebx, 1
sub      ebx, 1
pxor     xmm5, xmm5
pinsrd   xmm5, ebx, 2
blendvps xmm0, xmm4, xmm5

代替你的

test    ebp, 1
jnz @F
blendps xmm0, xmm4, 4

上面的代码计算 ebx = ( ebp & 1 ) ? 0 : -1;,将该整数插入零向量的第 3 channel ,并将该向量用于 blendvps 指令中的选择器。

  1. 不需要这条指令:pcmpgtd xmm0, xmm1 连同上一个和下一个,它计算这个:
xmm0 = _mm_cmplt_epi32( xmm1, _mm_setzero_si128() );
xmm0 = _mm_blendv_ps( xmm0, xmm3, xmm0 );

这是一个等价物:

xmm0 = _mm_blendv_ps( _mm_setzero_si128(), xmm3, xmm1 );

该比较指令针对 xmm1 < 0 比较 int32 channel 。这导致这些整数的符号位。 _mm_blendv_ps 指令仅测试 32 位 channel 中的高位,您实际上不需要在此之前比较 xmm1 < 0。

  1. 除非您需要支持不带 AVX 的 CPU,否则您应该对指令使用 VEX 编码,即使是处理 16 字节向量的代码也是如此。您的 SIMD 代码使用传统编码,其中大多数采用 2 个参数并将结果写入第一个参数。大多数 VEX 指令采用 3 个参数并将结果写入另一个参数。这应该摆脱像 movdqa xmm4, xmm0 这样的冗余移动指令。

关于assembly - 为什么 SIMD 比标量对应物慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67761813/

相关文章:

gcc -masm=intel 给我小写助记符

gcc - mulx 指令的固有特性

assembly - 我如何使用 NASM 获取用户输入?

linux - 在汇编中进行除法时出现浮点异常(核心已转储)

c - 使用代码矢量化的矩阵运算

c++ - SSE 指令健全性检查

assembly - 映射内存和 SSE

当循环迭代次数不恒定时,分支预测器能否完美预测?

c - 0..9 约束在 GCC 内联汇编中有什么作用?

debugging - 如何运行单行汇编,然后查看 [R1] 和条件标志