您是否将 x86-64 与 AVX2 一起用于 256 位向量?我认为这是一个有趣的案例。
如果是这样,您可以使用饱和减法和可变计数移位在几条指令中执行此操作。
x86 SIMD移位就像vpsrlvq
使移位计数饱和一样,当计数 >= 元素宽度时将所有位移出。与整数移位不同,移位计数被屏蔽(因此环绕)。
对于最低的u64
元素,从全一开始,我们需要在bitpos
>= 64 时保持不变。或者对于较小的位位置,将其右移64-bitpos
. 正如您所观察到的,无符号饱和减法看起来像是为更大的位位置创建 0 的移位计数的方法。但是 x86 只有 SIMD 饱和减法,并且仅适用于字节或字元素。但如果我们不关心 bitpos > 256,那很好,我们可以在每个 u64 的底部使用 16 位元素,并0-0
在u64
.
您的代码看起来非常复杂,创建(1<<n) - 1
和异或。 我认为直接在元素上使用可变计数移位要容易得多。0xFFFF...FF
我不认识 Zig,所以尽你所能让它发出这样的 asm。希望这很有用,因为您标记了此程序集;应该很容易转换为 C 或 Zig 的内在函数(如果有的话)。
default rel
section .rodata
shift_offsets: dw 64, 128, 192, 256 ; 16-bit elements, to be loaded with zero-extension to 64
section .text
pos_to_mask256:
vpmovzxwq ymm2, [shift_offsets] ; _mm256_set1_epi64x(256, 192, 128, 64)
vpcmpeqd ymm1, ymm1,ymm1 ; ymm1 = all-ones
; set up vector constants, can be hoisted
vmovd xmm0, edi
vpbroadcastq ymm0, xmm0 ; ymm0 = _mm256_set1_epi64(bitpos)
vpsubusw ymm0, ymm2, ymm0 ; ymm0 = {256,192,128,64}-bitpos with unsigned saturation
vpsrlvq ymm0, ymm1, ymm0 ; mask[i] >>= count, where counts >= 64 create 0s.
ret
如果输入整数在内存中开始,您当然可以有效地将其广播加载到 ymm 寄存器中。
shift-offsets 向量当然可以从循环中提升出来,全1也可以。
输入 = 77 时,高 2 个元素通过 256-77=179 和 192-77=115 位的移位归零。用 NASM + GDB 测试 EDI=77,结果是
(gdb) p /x $ymm0.v4_int64
{0xffffffffffffffff, 0x1fff, 0x0, 0x0}
GDB 首先打印低元素,与英特尔符号/图表相反。这个向量实际上是 0, 0, 0x1fff, 0xffffffffffffffff
,即 64+13 = 77 个一位,其余全为零。其他测试用例
edi=0
: 掩码 = 全零
edi=1
: 掩码 = 1
- ... :掩码 =
edi
底部一位,然后为零
edi=255
: mask = 除了顶部元素的最高位之外的所有1
edi=256
: 掩码 = 全部
edi>256
:掩码=全。(无符号减法处处饱和为 0。)
您需要 AVX2 进行可变计数班次。 psubusb/w
是 SSE2,因此您可以考虑使用 SIMD 执行该部分,然后返回到标量整数进行移位,或者一次只对一个元素使用 SSE2 移位。就像psrlq xmm1, xmm0
将 xmm1 的所有元素的低 64 位xmm0
作为移位计数一样。
大多数 ISA没有饱和标量减法。我认为,一些 ARM CPU 可以用于标量整数,但 x86 没有。IDK 你正在使用什么。
在 x86(和许多其他 ISA)上,您有 2 个问题:
- 为低元素保留全一(修改移位结果,或将移位计数饱和为 0)
- 生成
0
高于包含掩码最高位的元素。x86 标量移位根本无法做到这一点,因此您可以在这种情况下为移位提供输入0
。也许用来根据for或其他东西cmov
设置的标志来创建它。sub
192-w
count = 192-w;
shift_input = count<0 ? 0 : ~0ULL;
shift_input >>= count & 63; // mask to avoid UB in C. Optimizes away on x86 where shr does this anyway.
嗯,这并不能处理将减法饱和到 0 以保持全一。
如果针对 x86 以外的 ISA 进行调整,也许可以查看其他一些选项。或者也许 x86 上也有更好的东西。创建全一或全零sar reg,63
是一个有趣的选项(广播符号位),但当192-count
符号位 = 0 时,我们实际上需要全一。