什么是最有效的代码来签署扩展大整数?

时间:2014-01-12 16:32:43

标签: assembly x86-64 performance

我正在用x86-64汇编语言编写代码库,为s0128s0256s0512提供所有传统的按位,移位,逻辑,比较,算术和数学函数,s1024s2048s4096有符号整数类型和f0128f0256f0512f1024,{{ 1}}和f2048浮点类型。

现在我正在编写一些类型转换例程,并且遇到了一些应该是微不足道的事情,但需要的指令比我预期的多得多。我觉得我必须遗漏一些东西(一些说明)才能让这更容易,但到目前为止还没有运气。

f4096结果的低128位只是s0256输入参数的副本,而s0128结果的高128位中的所有位必须是设置为s0256输入参数中最重要的位。

简单,对吧?但到目前为止,我可以将s0128转换为s0256。忽略前4行(它们只是参数错误检查)和最后2行(从函数返回没有错误(rax == 0))。中间的5行是有问题的算法。尽量避免[条件]跳转指令。

s0128

该例程也是非最优的,因为每条指令都需要前一条指令的结果,这会阻止任何指令的并行执行。

是否有更好的指令来使用符号扩展进行右移?我找不到像.text .align 64 big_m63: .quad -63, -63 # two shift counts for vpshaq instruction big_s0256_eq_s0128: # (s0256* arg0, const s0128* arg1); # s0256 = s0256(s0128) orq %rdi, %rdi # is arg0 a valid address ??? jz error_argument_invalid # nope orq %rsi, %rsi # is arg1 a valid address ??? jz error_argument_invalid # nope vmovapd (%rsi), %xmm0 # ymm0 = arg1.ls64 : arg1.ms64 : 0 : 0 vmovhlps %xmm0, %xmm0, %xmm1 # ymm1 = arg1.ms64 : arg1.ms64 : 0 : 0 vpshaq big_m63, %xmm1, %xmm1 # ymm1 = arg1.sign : arg1.sign : 0 : 0 vperm2f128 $32, %ymm1, %ymm0, %ymm0 # ymm1 = arg1.ls64 : arg1.ms64 : sign : sign vmovapd %ymm0, (%rdi) # arg0 = arg1 (sign-extended to 256-bits) xorq %rax, %rax # rax = 0 == no error ret # return from function 这样的指令接受一个立即字节来指定移位计数,虽然我不知道为什么(许多SIMD指令有各种目的的直接8位操作数)。此外,英特尔不支持vpshaq。糟糕!

但是看! StephenCanon在下面对这个问题有一个很好的解决方案!真棒!该解决方案还有一个比上述更多的指令,但vpshaq指令可以放在第一个vpxor指令之后,并且应该有效地采用不超过上述5指令版本的周期。喝彩!

为了完整性和简单比较,以下是具有最新StephenCanon增强功能的代码:

vmovapd

我不确定,但不需要从内存中读取这两个64位移位计数也可能会略微加快代码速度。好的。

1 个答案:

答案 0 :(得分:3)

你的事情过于复杂。签到rax后,只需从那里做两个64b商店,而不是尝试在ymm0中汇总结果。少一条指令和一条短得多的依赖链。

当目的地类型变大时,当然,使用更宽的商店(AVX)是有意义的。使用AVX2,您可以使用vbroadcastq更有效地执行splat,但看起来您的目标是基线AVX?

我还应该注意到,一旦达到~512b整数,对于大多数算法而言,超线性操作(如乘法)的成本因此完全占据了运行时间,因此在符号扩展等操作中挤压每个最后一个周期会迅速失去价值。这是一个很好的练习,但是一旦你的实施“足够好”,最终不能最有效地利用你的时间。


经过进一步思考,我有以下建议:

vmovhlps  %xmm0, %xmm0, %xmm1 // could use a permute instead to stay in integer domain.
vpxor     %xmm2, %xmm2, %xmm2
vpcmpgtq  %xmm1, %xmm2, %xmm2 // generate sign-extension without shift

这具有(a)不需要恒定负载和(b)在Intel和AMD上工作的优点。生成零的xor看起来像一个额外的指令,但实际上这个归零的习惯用法甚至不需要在最近的处理器上执行插槽。


FWIW,如果定位AVX2,我可能会这样写:

vmovdqa (%rsi),        %xmm0 // { x0, x1, 0,  0  }
vpermq   $0x5f, %ymm0, %ymm1 // { 0,  0,  x1, x1 }
vpxor    %ymm2, %ymm2, %ymm2 // { 0,  0,  0,  0  }
vpcmpgtq %ymm1, %ymm2, %ymm2 // { 0,  0,  s,  s  } s = sign extension
vpor     %ymm2, %ymm0, %ymm0 // { x0, x1, s,  s  }
vmovdqa  %ymm0,       (%rdi)

不幸的是,我不认为AMD可以使用vpermq