Algorithm 使用AVX2更快地查找表

Algorithm 使用AVX2更快地查找表,algorithm,performance,optimization,sse,simd,x86,Algorithm,Performance,Optimization,Sse,Simd,X86,我正在尝试加速一个执行一系列查找表的算法。我想使用SSE2或AVX2。我尝试过使用_mm256_i32gather_epi32命令,但速度慢了31%。是否有人对任何改进或不同的方法有任何建议 时间: C代码=234 聚集=340 static const int32_t g_表[2][64];//值介于0和63之间 模板 静态无效查找数据(int16_t*dst,t*src) { const int32_t*lut=g_表[which]; //将此代码留给Broadwell或Skylake,因为

我正在尝试加速一个执行一系列查找表的算法。我想使用SSE2或AVX2。我尝试过使用_mm256_i32gather_epi32命令,但速度慢了31%。是否有人对任何改进或不同的方法有任何建议

时间: C代码=234 聚集=340

static const int32_t g_表[2][64];//值介于0和63之间
模板
静态无效查找数据(int16_t*dst,t*src)
{
const int32_t*lut=g_表[which];
//将此代码留给Broadwell或Skylake,因为它比C代码慢31%
//(哈斯韦尔12分,布罗德韦尔7分,天湖5分)
#如果0
if(sizeof(T)=sizeof(int16_T)){
__m256i avx0、avx1、avx2、avx3、avx4、avx5、avx6、avx7;
__m128i sse0、sse1、sse2、sse3、sse4、sse5、sse6、sse7;
__m256i掩码=_mm256_set1_epi32(0xffff);
avx0=_mm256_loadu_si256((_m256i*)(lut));
avx1=mm256_loadu_si256((m256i*)(lut+8));
avx2=_mm256_loadu_si256((_m256i*)(lut+16));
avx3=_mm256_loadu_si256((_m256i*)(lut+24));
avx4=_mm256_loadu_si256((_m256i*)(lut+32));
avx5=_mm256_loadu_si256((_m256i*)(lut+40));
avx6=_mm256_loadu_si256((_m256i*)(lut+48));
avx7=_mm256_loadu_si256((_m256i*)(lut+56));
avx0=_mm256_i32gather_epi32((int32_t*)(src),avx0,2);
avx1=mm256_i32gather_epi32((int32_t*)(src),avx1,2);
avx2=_mm256_i32gather_epi32((int32_t*)(src),avx2,2);
avx3=_mm256_i32gather_epi32((int32_t*)(src),avx3,2);
avx4=_mm256_i32gather_epi32((int32_t*)(src),avx4,2);
avx5=_mm256_i32gather_epi32((int32_t*)(src),avx5,2);
avx6=_mm256_i32gather_epi32((int32_t*)(src),avx6,2);
avx7=_mm256_i32gather_epi32((int32_t*)(src),avx7,2);
avx0=_mm256_和_si256(avx0,掩码);
avx1=_mm256_和_si256(avx1,掩码);
avx2=_mm256_和_si256(avx2,掩码);
avx3=_mm256_和_si256(avx3,掩码);
avx4=_mm256_和_si256(avx4,掩码);
avx5=_mm256_和_si256(avx5,掩码);
avx6=_mm256_和_si256(avx6,掩码);
avx7=_mm256_和_si256(avx7,掩码);
sse0="mm"packus"epi32(mm256)castsi256(avx0),"mm256)extracti128(avx0,1);;
sse1="mm"packus"epi32(mm256)castsi256(avx1),"mm256)extracti128(avx1,1);;
sse2="mm"packus"epi32(mm256)castsi256(avx2),"mm256)extracti128(avx2,1);;
sse3=_mm_packus_epi32(_mm256_castsi256_si128(avx3),_mm256_extracti128_si256(avx3,1));
sse4=_mm_packus_epi32(_mm256_castsi256_si128(avx4),_mm256_extracti128_si256(avx4,1));
sse5=_mm_packus_epi32(_mm256_castsi256_si128(avx5),_mm256_extracti128_si256(avx5,1));
sse6=_mm_packus_epi32(_mm256_castsi256_si128(avx6),_mm256_extracti128_si256(avx6,1));
sse7=_mm_packus_epi32(_mm256_castsi256_si128(avx7),_mm256_extracti128_si256(avx7,1));
_mm_-storeu_-si128((_-m128i*)(dst),sse0);
_mm_-storeu_-si128((_-m128i*)(dst+8),sse1);
_mm_-storeu_-si128((_-m128i*)(dst+16),sse2);
_mm_-storeu_-si128((_-m128i*)(dst+24),sse3);
_mm_-storeu_-si128((_-m128i*)(dst+32),sse4);
_mm_-storeu-si128((_-m128i*)(dst+40),sse5);
_mm_-storeu_-si128((_-m128i*)(dst+48),sse6);
_mm_-storeu_-si128((_-m128i*)(dst+56),sse7);
}
其他的
#恩迪夫
{
对于(int32_t i=0;i<64;i+=4)
{
*dst++=src[*lut++];
*dst++=src[*lut++];
*dst++=src[*lut++];
*dst++=src[*lut++];
}
}
}

你说得对,在Haswell上,gather比
PINSRD
循环慢。布罗德韦尔的情况可能接近收支平衡。(特别是性能链接,请参见标记wiki)


如果您的索引很小,或者您可以将它们切片,
pshufb
可以用作具有4位索引的并行LUT
。它提供16个8位的表条目,但您可以使用punpcklbw之类的工具将两个字节结果向量组合成一个16位结果向量。(LUT项的高半部和低半部的单独表格,具有相同的4位索引)

当您想要将GF16值的大缓冲区中的每个元素乘以相同的值时,这种技术可用于Galois字段乘法。(例如,对于Reed-Solomon纠错代码)正如我所说,利用这一点需要利用用例的特殊属性


AVX2可以在256b矢量的每个车道上并行执行两个128b
pshufb
s。在AVX512F之前没有更好的版本:。在AVX512VBMI中有字节(
vpermi2b
),字(
vpermi2w
,在AVX512BW中),dword(这一个,
vpermi2d
,在AVX512F中)和qword(
vpermi2q
)元素大小版本。这是一个完整的交叉通道混洗,索引到两个串联的源寄存器中。(就像AMD XOP的
vpperm

一个内在函数(
vpermt2d
/
vpermi2d
)后面的两个不同指令为您提供了使用结果覆盖表或覆盖索引向量的选择。编译器将根据重用的输入进行选择


您的具体案例: 查找表实际上是
src
,而不是您调用的
lut
变量
lut
实际上正在遍历一个数组,该数组用作
src
的洗牌控制掩码

您应该使
g_表
成为
uint8_t
的数组,以获得最佳性能。条目仅为0..63,因此它们适合。零扩展加载到完整寄存器和普通加载一样便宜,所以它只是减少了缓存占用空间。要与AVX2采集一起使用,请使用
vpmovzxbd
。内在函数很难用作负载,因为没有形式接受<代码
*dst++ = src[*lut++];