我认为您的第一个 idea 可能最好在一个循环中完成,该循环可以分摊加载向量常量的成本.AVX-512可以有效地做到这一点.
或者使用向量加载,然后用vpbroadcastd
单独广播低元素,或者使用向量加载和广播加载.例如vpaddd zmm16, zmm31, [rdi]{1to16}
/vpcmpeqd k1, zmm16, [rdi]
.
嗯,但然后判断所有元素是否为真,我猜可能是kaddw
与常量1
,并判断低16位是否为0与kortest
?或者仅将kmov
与0xffff
进行比较,就像我们在SSE/AVX pmovmskb
中所做的那样.我试过了,克朗有一个更好的主意:比较不相等,并判断掩码是否全为零.(即,通过判断每个元素是否不相等来判断它们是否相等.)这允许在掩码本身上使用kortest
.我把朗的 idea 应用到我的本质上,这样GCC也可以做得更好.
在C++中:
#include <immintrin.h>
// compare for not-equal, checking the mask for 0
bool check_contig(int *p)
{
__m512i bcast_first = _mm512_set1_epi32(*p);
__m512i desired = _mm512_add_epi32(bcast_first, _mm512_setr_epi32(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0));
__m512i v = _mm512_loadu_si512(p);
__mmask16 cmp = _mm512_cmpneq_epi32_mask(desired, v);
return cmp == 0;
}
GCC和Cang的Godbolt-ASM:
# GCC
check_contig(int*):
vmovdqa32 zmm0, ZMMWORD PTR .LC0[rip]
vpaddd zmm0, zmm0, DWORD PTR [rdi]{1to16}
vpcmpd k0, zmm0, ZMMWORD PTR [rdi], 4
kortestw k0, k0
sete al
vzeroupper
ret
# clang
check_contig(int*):
vpbroadcastd zmm0, dword ptr [rdi]
vpaddd zmm0, zmm0, zmmword ptr [rip + .LCPI0_0]
vpcmpneqd k0, zmm0, zmmword ptr [rdi]
kortestw k0, k0
sete al
vzeroupper
ret
因此,它们都 Select 加载两次而不是vpbroadcastd zmm1, xmm0
次,至少在不在循环中时是这样,因此向量常量也必须从.rodata
开始加载.
也许如果我把它写成_mm512_broadcastd_epi32( _mm512_castsi512_si128(v))
,他们会更喜欢一个加载,代价是一个额外的洗牌.(当您有512位微处理器在运行时,情况可能会更糟,因此Intel CPU关闭了端口1上的矢量ALU,只留下端口0和5.https://agner.org/optimize/和https://uops.info/)
ALGO B-避免非平凡向量常量
也许您的第二种方法也可以高效地使用valignd
来旋转向量;它需要的唯一向量常量是全一,它可以更便宜地生成(vpternlogd
)而不是加载.
判断比较掩码可能需要kmov
到整数,and
+cmp
才能判断除一位以外的所有位,除非我们可以使用相同的技巧clang进行排列,以便我们实际上希望掩码在我们想要的位置为全零.在这种情况下,test eax, imm32
可以判断我们想要的位,而忽略我们不想要的位.