【问题标题】:Why is SIMD slower than scalar counterpart为什么 SIMD 比标量对应物慢
【发布时间】:2021-08-18 01:20:54
【问题描述】:

这是另一个SSE is slower than normal code! Why? 类型的问题。
我知道有很多类似的问题,但它们似乎与我的情况不符。

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

我真的很困惑,一个所谓的 1 延迟 1c 吞吐量 1uops 指令 psrldq 需要超过 3 延迟 0.5c 吞吐量 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

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

【问题讨论】:

  • why do single uops instructions like paddd/psubd/psrldq/movddup take so long to execute - 一条指令上的高样本数并不意味着它是那个特定的指令很慢。您的 CPU 出现故障和超标量,同时有许多指令在执行,样本不会很准确,需要进一步分析以了解高百分比在指令级粒度上可能意味着什么。
  • 如果不深入研究算法,您的 SIMD 代码似乎通过所有 pmuludq 和以下说明进行依赖,这意味着代码受延迟限制。在标量代码中情况并非如此,mulx/imul 可以并行执行。其余指令成本较低,因此乘法序列对循环性能有很大贡献。
  • 哦,对了 psrldq 使用字节单位进行移位,我明白你做了什么。我对问题顶部发布的算法感到困惑。我认为仅使用 SIMD 并行执行两个计算不会带来太多好处,因为标量代码已经使用了两个 dep 链(ad Andrey 指出)并且不需要所有的洗牌。我不知道 VS 是如何分析代码的,但是 pxor xmm0, xmm0 以红色显示,并且计数很高(什么?),这真的很奇怪。
  • @stepan 我认为它被一条指令关闭了。根本不可能pxor 花费那么多。如果你这样想,那是有道理的。 psrldq 是罪魁祸首,而不是 pxor
  • @stepan:同意 quaver,那些像素归零计数很可能属于psrldq。还有一种“偏斜”效应,即后来的指令受到指责。例如当计数器溢出时,一条指令正在等待一个缓慢的结果,并且中断在它退休后处理,归咎于下一条指令。 pxor-zeroing 是对 Zen 的依赖破坏,虽然它确实需要一个执行单元来写入零(不像 Sandybridge-family,它实际上和 nop 一样便宜),但除非有一些时髦的前面,否则它仍然没有显着的成本-结束效果。 (不太可能)。

标签: assembly x86 sse simd


【解决方案1】:
  1. 您的 SIMD 代码浪费时间错误地预测了 test 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;,将该整数插入零向量的第三通道,并将该向量用于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 _mm_blendv_ps 指令只测试 32 位通道中的高位,您不需要在此之前比较 xmm1

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

【讨论】:

  • @stepan 很好。已更新。
  • 由于某种原因,与 AVX 对应的代码运行得更慢。 blendvps 也要求 xmm0 作为第三个操作数,所以我不得不添加额外的 movaps
  • @quaver:AVX 将允许一些优化,但您必须在手写 asm 中手动利用它;没有看到你的变化,IDK 为什么它会变慢。 (我忘记了 Zen2 是否有 SSE/AVX 转换惩罚,但仅与 128 位 AVX 指令无关。当然,Zen2 有 256 位向量 ALU,所以如果你也可以假设 AVX2,请使用它们。)另见@ 987654321@ 用于 52 位整数 FMA 的使用双精度尾数的技巧。
  • 请注意,AVX VBLENDVPS xmm1, xmm2, xmm3/m128, xmm4 将混合控制寄存器编号作为立即操作数,因此它不会使用 xmm0/ymm0 卡住。 (felixcloutier.com/x86/blendvps)。 (有趣的事实:在 Intel Skylake 上,blendvps xmm 是单 uop,但 vblendvps 是 2 uop。不过,Zen 总是将它作为单 uop 运行。uops.info/table.html。Zen2 的整数 vpblendvb 也是单 uop,但不能在尽可能多的端口上运行)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-25
  • 2021-09-04
  • 1970-01-01
  • 1970-01-01
  • 2021-04-23
  • 2011-03-23
相关资源
最近更新 更多