我正在用x86-64汇编语言编写一个代码库,以提供s0128
,s0256
,s0512
,s1024
,s2048
的所有常规按位,移位,逻辑,比较,算术和数学函数,和s4096
有符号整数类型以及f0128
,f0256
,f0512
,f1024
,f2048
和f4096
浮点类型。
现在,我正在编写一些类型转换例程,并且遇到了一些琐碎的事情,但所需的指令却比我期望的多得多。我觉得我一定想念一些东西(一些说明)来简化这个过程,但是到目前为止还算不上什么。s0256
结果的低128位只是s0128
输入参数的副本,并且s0256
结果的高128位中的所有位都必须设置为s0128
结果中的最高有效位。 s0256
输入参数。
简单吧?但是,到目前为止,这是我能想到的最好的将s0128
转换为vpshaq
的方法。忽略前4行(它们只是参数错误检查)和后2行(无错误地从函数返回(rax == 0))。中间的5行是相关算法。尽量避免[条件]跳转指令。
.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
该例程也是非最佳的,因为每条指令都需要前一条指令的结果,这会阻止并行执行任何指令。
有没有更好的指令来右移符号扩展名?我找不到类似
vpshaq
的指令来接受立即数字节来指定移位计数,尽管我不知道为什么(许多SIMD指令出于各种目的都具有立即数8位操作数)。另外,英特尔不支持vpxor
。糟糕!但看! StephenCanon在下面对这个问题有一个绝妙的解决方案!太棒了!该解决方案比上述解决方案多一条指令,但是
vmovapd
指令可以放在第一条指令之后,并且实际上不应占用比上述5条指令版本更多的周期。太棒了!为了完整性和容易比较,下面是具有最新StephenCanon增强功能的代码:
.text
.align 64
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
vpxor %xmm2, %xmm2, %xmm2 # ymm2 = 0 : 0 : 0 : 0
vmovhlps %xmm0, %xmm0, %xmm1 # ymm1 = arg1.ms64 : arg1.ms64 : 0 : 0
vpcmpgtq %xmm1, %xmm2, %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
我不确定,但是不必从内存中读取这两个64位移位计数也可能会稍微加快代码的速度。真好
最佳答案
您使事情变得过于复杂。一旦在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)
不幸的是,我认为
vpermq
在AMD上不可用。