AVX2,如何有效地将四个整数加载到偶数256位寄存器的指数并复制到奇数指数?

时间:2016-07-05 16:37:12

标签: x86 sse simd avx avx2

我在内存中有一个对齐的整数数组,包含索引I0,I1,I2,I3。我的目标是将它们放入包含I0,I0 + 1,I1,I1 + 1,I2,I2 + 1,I3,I3 + 1的__m256i寄存器中。困难的部分是将它们作为I0,I0进入256位寄存器,I1,I1,I2,I2,I3,I3,之后我可以添加一个包含0,1,0,1,0,1,0,1的寄存器。

我发现了内部的_mm256_castsi128_si256,它允许我将4个整数加载到256位寄存器的低128位,但我很难找到最好的内在函数从那里使用。

任何帮助将不胜感激。我可以访问所有SSE版本,AVX和AVX2,并且只想使用内在函数来执行此操作。

编辑:

我认为这很有效,但在测试它的过程中,我的效率并不高。

// _mm128_load_si128: Loads 4 integer values into a temporary 128bit register.
// _mm256_broadcastsi128_si256: Copies 4 integer values in the 128 bit register to the low and high 128 bits of the 256 bit register.
__m256i tmpStuff = _mm256_broadcastsi128_si256 ((_mm_load_si128((__m128i*) indicesArray)));

// _mm256_unpacklo_epi32: Interleaves the integer values of source0 and source1.
__m256i indices = _mm256_unpacklo_epi32(tmpStuff, tmpStuff);

__m256i regToAdd = _mm256_set_epi32 (0, 1, 0, 1, 0, 1, 0, 1);
indices = _mm256_add_epi32(indices, regToAdd);

Edit2:上面的代码不起作用,因为_mm256_unpacklo_epi32的行为与我的想法不同。上面的代码将导致I0,I0 + 1,I1,I1 + 1,I0,I0 + 1,I1,I1 + 1.

Edit3:以下代码有效,但我不确定它是否最有效:

__m256i tmpStuff = _mm256_castsi128_si256(_mm_loadu_si128((__m128i*) indicesArray));
__m256i mask = _mm256_set_epi32 (3, 3, 2, 2, 1, 1, 0, 0);
__m256i indices= _mm256_permutevar8x32_epi32(tmpStuff, mask);
__m256i regToAdd = _mm256_set_epi32 (1, 0, 1, 0, 1, 0, 1, 0); // Set in reverse order.
indices= _mm256_add_epi32(indices, regToAdd);

1 个答案:

答案 0 :(得分:8)

您的_mm256_permutevar8x32_epi32版本看起来非常适合英特尔CPU,除非我错过了将shuffle折叠成128b负载的方法。这可能对融合域uop吞吐量略有帮助,但对于未融合域则无效。

1次加载(vmovdqa),1次shuffle(vpermd,又名_mm256_permutevar8x32_epi32)和1次加载(vpaddd)非常轻量级。在英特尔,交叉路由的shuffle具有额外的延迟但没有更差的吞吐量。在AMD Ryzen,交叉洗牌更加昂贵。 (http://agner.org/optimize/)。

由于您可以使用AVX2,如果为vpermd加载随机播放掩码不是问题,那么您的解决方案就很棒。 (注册压力/缓存未命中)。

请注意_mm256_castsi128_si256并不能保证__m256i的高半部分全部为零。但是你并不依赖于此,所以你的代码完全没问题。

BTW,您可以使用一个256位加载,并使用vpermd以不同的方式解压缩。使用另一个mask,所有元素4更高。

另一种选择是未对齐的256b负载,在4个元素的中间包含泳道分割,因此您在高位底部有2个元素,低位顶部有2个元素车道。然后,您可以使用内置shuffle将数据放在需要的位置。但是在每个通道中它都是一个不同的shuffle,所以你仍然需要一个shuffle,它将控制操作数放在一个寄存器(不是一个立即数)中,在一次操作中完成它。 (vpshufdvpermilps imm8为两个通道循环使用相同的立即数。)直接影响上/下通道的不同位的唯一混洗是qword粒度混洗,如vpermq({{ 1}},而不是_mm256_permutex_epi64)。

您可以使用vpermilps ymm,ymm,ymmpermutexvarvpshufb)来实现这一目标,这将在Ryzen上比跨越_mm256_shuffle_epi8更高效(可能是3 uops /根据{{​​3}}

,每4c吞吐量为1,如果它与vpermd相同

但是当你的数据已经对齐时,使用未对齐的负载并不吸引人,并且所有增益都是在车道内与车道交叉的混乱。如果您需要16位或8位粒度混洗,那么它可能是值得的(因为在AVX512之前没有交叉字节或字随机播放,并且在Skylake-AVX512上vpermps是多个uops 。)

避免shuffle-mask矢量常量的替代方案,但性能更差(因为它需要两倍的shuffle):

Agner Fog是将上面的两个元素放入上部128位通道的另一种选择。

vpermw

或者,如果shuffle端口是整个循环的瓶颈,则可能比上面的2-shuffle版本更高的吞吐量。 (但仍然比; slow, not recommended. Avoids using a register for shuffle-control, though. vpmovzxdq ymm0, [src] vpshufd ymm1, ymm0, _MM_SHUFFLE(2,2, 0,0) ; duplicate elements vpaddd ... 版本差。)

vpermd

这有一些指令级并行:OR可以与shift并行运行。但是更多的uops仍然很糟糕;如果你没有使用矢量regs,那么最好使用内存中的shuffle-control向量。