AVX有在__m256i
向量中插入和提取16位和32位整数的指令:_mm256_insert_epi16
、_mm256_insert_epi32
、_mm256_extract_epi16
、_mm256_extract_epi32
。
然而,AVX-512似乎没有同等的指令.对于__m512i
向量,实现这些方法的适当方法是什么?即
__m512i _mm512_insert_epi16(__m512i a, __int16 i, int index)
__m512i _mm512_insert_epi32(__m512i a, __int32 i, int index)
int _mm512_extract_epi16(__m512i a, int index)
int _mm512_extract_epi32(__m512i a, int index)
发布于 2019-10-09 13:12:25
相关信息:
vpblendw
重复了两条车道的混合控制,但与vpblendd
不同)。而这并不能利用AVX512的优势,比如合并蒙面广播。AVX有instructions,用于在__m256i向量中插入和提取16位和32位整数:
不,它没有,_mm256_insert_epi16
和 epi32
本质是“假的”;它们必须被多个指令模仿,就像_mm_set_epi32(a,b,c,d)
不是任何单一指令的内在一样。
IDK为什么英特尔选择为AVX2 1/2而不是AVX512版本提供它们;也许他们后来意识到他们不应该为AVX2提供这些版本,以避免欺骗那些假设那些本质只需要一次洗牌的人编写效率低下的代码。但是他们不能在不破坏现有代码的情况下删除现有的代码。
不幸的是,vpinsrd ymm_dst, ymm_src, r/m32, imm8
(或ZMM)不存在,只有xmm。(https://www.felixcloutier.com/x86/pinsrb:pinsrd:pinsrq)。XMM版本在__m256i
上是不可用的,因为它对上面的128位进行零点。请参阅使用ymm寄存器作为“类似内存”的存储位置。 (您可以使用pinsrd xmm, r/m32, imm
的遗留SSE编码将其插入到YMM的低128位中,但由于SSE/AVX过渡刑罚在那里的工作方式,在哈斯韦尔和冰湖上的速度太慢了。但对Skylake或Ryzen没问题。不过,编译器永远不会发出这样的信息。)
_mm256_insert_epi32
可以使用AVX2编译到广播加载,vpblendd
可以从内存中插入一个dword。或者更糟的是,使用寄存器中的整数,编译器可能会将其vmovd
到xmm reg,将其广播到YMM,然后进行混合。(就像我在m256i向量中展示的手工操作一样)
“适当的”实现取决于周围的代码.
如果要插入的元素超过一个,则可能需要在插入之前将它们混合在一起。甚至考虑向量存储,多标量存储,然后向量重新加载,尽管存储转发失速。或者,如果延迟临界路径通过向量,而不是标量,则标量存储/向量重新加载来提供混合数据。如果你有很多小的标量元素,这可能是值得的。
然而,对于单个,AVX512F实际上有一些很好的功能:它有两个输入洗牌,比如vpermt2d
,您可以使用它将一个元素从一个x/y/zmm的底部插入到另一个向量中的任何位置(将另一个向量中的所有其他目标元素作为源)。
,但这里最有用的是蒙面广播: uops.info确认,VPBROADCASTW zmm0{k1}, eax
是一个单一的uop指令,从向量到向量(用于合并)和从掩码到向量的三个周期延迟。<= 5周期延迟从eax到合并结果。唯一的问题是设置掩码,但希望它能被提升出一个不变量插入位置的循环。
#include <immintrin.h>
#include <stdint.h>
__m512i _mm512_insert32(__m512i target, uint32_t x, const int pos)
{
return _mm512_mask_set1_epi32(target, 1UL<<pos, x);
}
将论哥德波特编译到此asm:
# gcc8.3 -O3 -march=skylake-avx512
_mm512_insert32(long long __vector(8), unsigned int, int):
mov eax, 1
shlx eax, eax, esi
kmovw k1, eax # mask = 1<<pos
vpbroadcastd zmm0{k1}, edi
ret
(gcc9浪费额外的指令,无缘无故地复制ESI )。
使用编译时间常数pos
,您可以得到像mov eax,2
/kmovw k1, eax
这样的代码;蒙面广播可能仍然是最好的选择。
这种方法适用于8、16、32或64位元素。8和16当然需要AVX512BW来进行vpbroadcastb/w
窄广播,而32和64只需要AVX512F。
摘录:
只需将您想要的元素洗牌到__m512i
的底部,在那里您可以使用_mm_cvtsi128_si32
。(_mm512_castsi512_si128
后)一个有用的洗牌是valignd
通过dword元素来移位或旋转,这样可以有效地将任何元素放到向量的底部,而不需要矢量控制。https://www.felixcloutier.com/x86/valignd:valignq
发布于 2019-10-18 12:03:07
要完成彼得的回答,以下是16位和32位插入/提取方法的实现:
#if defined(__GNUC__)
int _mm512_cvtsi512_si32(__m512i a)
{
__v16si b = (__v16si) a;
return b[0];
}
#endif
__m512i _mm512_insert_epi16(__m512i target, const std::int16_t x, const int index)
{
return _mm512_mask_set1_epi16(target, 1UL << index, x);
}
static inline __m512i _mm512_insert_epi32(__m512i target, const std::int32_t x, const int index)
{
return _mm512_mask_set1_epi32(target, 1UL << index, x);
}
template <int index>
int _mm512_extract_epi32(__m512i target)
{
return _mm512_cvtsi512_si32(_mm512_alignr_epi32(target, target, index));
}
template <int index>
int _mm512_extract_epi16(__m512i target)
{
return (_mm512_extract_epi32<index / 2>(target) >> (index % 2 ? 16 : 0)) & 0xFFFF;
}
见示例。
https://stackoverflow.com/questions/58303958
复制相似问题