我正在用x86-64汇编语言编写一个代码库,以提供s0128s0256s0512s1024s2048的所有常规按位,移位,逻辑,比较,算术和数学函数,和s4096有符号整数类型以及f0128f0256f0512f1024f2048f4096浮点类型。

现在,我正在编写一些类型转换例程,并且遇到了一些琐碎的事情,但所需的指令却比我期望的多得多。我觉得我一定想念一些东西(一些说明)来简化这个过程,但是到目前为止还算不上什么。

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上不可用。

08-16 11:14