如何检查 16 的对齐块是否u32
连续(并且递增)?
例如:[100, 101, 102, ..., 115]
是。并且,[100, 99, 3 ...]
不是。
我使用的是 AVX512f。这是我到目前为止所拥有的:
算法A:
* predefine DECREASE_U32, a u32x16 of [15,14,13,...0]
* let a = input + DECREASE_32 // wrapping is OK
* compare a to u32x16::splat(first_item(a))
* Return whether all true
替代方案(算法 B)
* let b = copy of A
* permute the elements of b by one position
* let b = a-b
* Is b all 1's (except for 1st position)
我在 Rust 中使用packed_simd
板条箱执行此操作,但任何语言/伪代码都可以。(我希望有一个 SIMD 操作来减去相邻项。)
我认为你的第一个想法可能是最好的,如果在一个循环内完成,可以分摊加载向量常量的成本。AVX-512 可以有效地做到这一点。
要么使用矢量加载,然后使用 单独广播低元素
vpbroadcastd
,或者使用矢量加载和广播加载。例如vpaddd zmm16, zmm31, [rdi]{1to16}
/vpcmpeqd k1, zmm16, [rdi]
。嗯,但是然后检查所有元素是否为真,我想也许
kaddw
用一个常量1
并用 ? 检查低 16 位是否为零kortest
?或者只是kmov
与整数寄存器进行比较,0xffff
就像我们对 SSE/AVX 所做的那样pmovmskb
。我尝试过,clang 有一个更好的主意:比较不等于,并检查掩码是否全为零。(即通过检查每个元素是否不相等来检查它们是否相等。)这允许kortest
掩码本身。我将 clang 的想法应用到我的内在函数中,这样 GCC 也可以做出更好的汇编。在 C++ 中:
Godbolt - 来自 GCC 和 clang 的 asm:
因此,它们都选择加载两次而不是
vpbroadcastd zmm1, xmm0
,至少在不在循环中时如此,因此向量常量也必须从 加载.rodata
。也许如果我以不同的方式写它,
_mm512_broadcastd_epi32( _mm512_castsi512_si128(v))
他们会更喜欢一次加载,但要付出额外的洗牌微指令的代价。(当您有 512 位 uops 运行时,情况可能会更糟,因此 Intel CPU 关闭端口 1 上的矢量 ALU,只留下端口 0 和 5。https://agner.org/optimize/ 和https ://uops .info/ )算法 B - 避免非平凡的向量常数
valignd
也许你的第二种方法也可以通过旋转向量来有效地完成;它需要的唯一向量常量是全一,可以更便宜地生成(vpternlogd
)而不是加载。检查比较掩码可能需要一个
kmov
到整数的and
+cmp
来检查除一位之外的所有内容,除非我们可以使用 clang 所做的相同技巧并安排事物,以便我们实际上希望掩码在我们想要的位置全为零。在这种情况下,test eax, imm32
可以检查我们想要的位,同时忽略我们不需要的位。我当前的 Rust 代码的核心现在是这个宏代码:
其中 $scalar 是
u32
, $simd 是u32x16
, $decrease 是 [15, 14 ... 0] 块。代码抽查的第一部分检查最后一个元素是否比第一个元素多 15(并处理溢出)。我请求一个智能工具来帮助我理解生成的 SIMD 程序集。它说: