使用Intel SIMD SSE加载非连续值

Pin*_*kPR 5 x86 assembly sse intel simd

我想加载一个128位寄存器与32位非连续浮点数.实际上,那些浮点数在内存中间隔128位.

所以如果内存看起来像那样:

| Float 0  | Float X | Float X | Float X |
| Float 4  | Float X | Float X | Float X |
| Float 8  | Float X | Float X | Float X |
| Float 12 | Float X | Float X | Float X |
Run Code Online (Sandbox Code Playgroud)

我想加载这样的矢量:

| Float 0  | Float 4 | Float 8 | Float 12 |
Run Code Online (Sandbox Code Playgroud)

zx4*_*485 5

如果您有 AVX2 可用,您可以使用VGATHERDPS指令来实现您的目标,这在这个 SO 答案中进行了解释。在您的情况下,您只需将索引向量初始化为 0,1,2,3,...(并使用收集寻址模式将其扩展到 0,4,8,12)。

.data
  .align 16
  ddIndices dd 0,1,2,3
  dpValues  REAL4 ...   ; replace 'dpValues' with your value array
.code
  lea        rsi, dpValues
  vmovdqa    xmm7, ddIndices

.loop:
  vpcmpeqw   xmm1, xmm1                 ; set to all ones
  vpxor      xmm0, xmm0                 ; break dependency on previous gather
  vgatherdps xmm0, [rsi+xmm7*4], xmm1
  ; do something with gather result in xmm0

  add        rsi, 16
  cmp        rsi, end_pointer
  jb      .loop                    ; do another gather with same indices, base+=16
Run Code Online (Sandbox Code Playgroud)

XMM1 是condition mask选择加载哪些元素的。

请注意,该指令在 Haswell 上速度不是那么快,但在 Broadwell 上实现速度更快,在 Skylake 上再次更快。

即便如此,在 Skylake 上使用聚集指令进行小步长加载可能只是 8 元素 ymm 向量的胜利。根据Intel 的优化手册11.16.4 收集指令的注意事项),当 L1D 缓存中的数据很热时,具有 4 元素向量的 Broadwell 硬件收集的最佳情况吞吐量为每个元素 1.56 个周期。


insertps在 AVX2 之前的架构上,如果不像这样单独加载所有值(使用 SSE4.1或) ,就无法(据我所知)执行此操作pinsrd

lea      esi, dpValues
movss    xmm0, [esi]          ; breaks dependency on old value of xmm0
insertps xmm0, [esi+4], 1<<4  ; dst element index in bits 5:4 of the imm8
insertps xmm0, [esi+8], 2<<4
insertps xmm0, [esi+12], 3<<4
Run Code Online (Sandbox Code Playgroud)

对于整数数据,最后一条指令是pinsrd xmm0, [esi+12], 3

没有 SSE4.1,将movss结果与unpcklps/一起打乱unpcklpd


Pet*_*des 5

希望您将其他数据用于某些事情,在这种情况下,加载所有内容并进行转置更有可能是有用的.

如果没有,那么只要数据在向量中有很多工作要做,SIMD根本就是可行的,因为将它打包到向量中是很昂贵的.


movss/ insertps如@ zx485所示,答案是"正常"的方式,就像你使用编译器时可能会得到的那样_mm_set_ps(f[12], f[8], f[4], f[0]);


当你的步幅恰好是4时,使用AVX你可以在两个负载上跨越所有四个浮子并混合.

(相关:什么是最快的步幅-3收集指令序列? 或者对于步幅2,它更值得做矢量加载和改组.)

vmovups   ymm1, [float0]                  ; float0 and float4 in the low element of low/high lanes
vblendps  ymm1, [float8 - 4], 0b00100010  ;  { x x f12 f4 | x x f8 f0 }
Run Code Online (Sandbox Code Playgroud)

这不是很好,因为您可能会与其中一个加载跨越缓存行边界.您可以vshufps ymm0, ymm1, [float8], 0b???????通过第二次加载实现类似的功能.

这可能是好的,具体取决于周围的代码,特别是如果你有AVX2 vpermps(带有一个随机控制矢量常数)或vpermpd(有一个立即)用于车道交叉shuffle将你想要的元素放入低128b通道.

没有AVX2用于跨车道洗牌,你需要vextractf128然后shufps.这可能需要进行一些规划才能将元素放在shufps可以将它们放在正确位置的位置.


当然,这一切都与内在函数一起使用,但它们需要更多的输入.