如何在两个 AVX2 向量之间交换 128 位部分

xtr*_*rtx 5 .net c# c++ avx2

问题:我有 4 x 256 位 AVX2 向量(A、B、C、D),我需要对它们各自的 128 位部分以及两个不同向量之间执行交换操作。这是我需要做的转变。

             Original                      Transformed
    || Low Lane || High Lane||     || Low Lane || High Lane||
A = ||    L1    ||    H1    || = > ||    L1    ||    L2    ||
B = ||    L2    ||    H2    || = > ||    H1    ||    H2    ||
C = ||    L3    ||    H3    || = > ||    L3    ||    L4    ||
D = ||    L4    ||    H4    || = > ||    H3    ||    H4    ||
Run Code Online (Sandbox Code Playgroud)

可视化

基本上我需要按以下顺序将输出存储到数组中:L1、L2、L3、L4、H1、H2、H3、H4。

我当前的解决方案使用:
4x _mm256_blend_epi32(最坏情况:延迟 1,吞吐量 0.35)
4x _mm256_permute2x128_si256(最坏情况:延迟 3,吞吐量 1)

// (a, c) = block0, (b, d) = block1
a = Avx2.Permute2x128(a, a, 1);
var template = Avx2.Blend(a, b, 0b1111_0000); // H1 H2
a = Avx2.Blend(a, b, 0b0000_1111); // L2 l1
a = Avx2.Permute2x128(a, a, 1); // L1 l2
b = template;

c = Avx2.Permute2x128(c, c, 1);
template = Avx2.Blend(c, d, 0b1111_0000); // H3 H4
c = Avx2.Blend(c, d, 0b0000_1111);  // L4 L3
c = Avx2.Permute2x128(c, c, 1); // L3 l4
d = template;

// Store keystream into buffer (in corrected order = [block0, block1])
Avx2.Store(outputPtr, a);
Avx2.Store(outputPtr + Vector256<uint>.Count, c);
Avx2.Store(outputPtr + Vector256<uint>.Count * 2, b);
Avx2.Store(outputPtr + Vector256<uint>.Count * 3, d);
Run Code Online (Sandbox Code Playgroud)

注意:如果您想知道的话,我正在使用 C#/NetCore 来执行 AVX2!请随意使用 C/C++ 中的示例。

有没有更好或更有效的方法来做到这一点?

编辑

接受答案为 C#

var tmp = Avx2.Permute2x128(a, b, 0x20);
b = Avx2.Permute2x128(a, b, 0x31);
a = tmp;
tmp = Avx2.Permute2x128(c, d, 0x20);
d = Avx2.Permute2x128(c, d, 0x31);
c = tmp;
Run Code Online (Sandbox Code Playgroud)

Jas*_*n R 5

如果我理解正确的话,我想你可以在没有这个 2x4 转置的混合指令的情况下逃脱,创建新的变量来选择你想要的车道。就像是:

__m256i a;    // L1 H1
__m256i b;    // L2 H2
__m256i c;    // L3 H3
__m256i d;    // L4 H4

__m256i A = _mm256_permute2x128_si256(a, b, 0x20);  // L1 L2
__m256i B = _mm256_permute2x128_si256(a, b, 0x31);  // H1 H2
__m256i C = _mm256_permute2x128_si256(c, d, 0x20);  // L3 L4
__m256i D = _mm256_permute2x128_si256(c, d, 0x31);  // H3 H4
Run Code Online (Sandbox Code Playgroud)

指令仍然有 3 个周期的延迟vperm2i128,但当数据跨越 128 位通道时,总会有这个延迟。这 4 个 shuffle 是独立的,因此它们可以流水线化(ILP);Intel 和 Zen 2 的吞吐量为 1/clock vperm2i128( https://agner.org/optimize/ , https://uops.info/

如果幸运的话,编译器将优化 L1、L2 和 L3、L4 洗牌,vinserti128AMD Zen 1 可以更高效地运行这些洗牌(1 个 uop,而不是 8 个;跨通道洗牌被分成多个 128 位 uop。)


这 4 个 shuffle 需要 4 个 uop 用于 shuffle 端口(Intel 上的端口 5);Intel 和 Zen2 对于这些 shuffle 仅有 1/clock shuffle 吞吐量。如果这将成为您循环中的瓶颈,请考虑 @chtz 的答案,该答案通过进行 2 次洗牌来排列需要移动的 4 个车道,以准备廉价的混合,从而花费更多的前端吞吐量 ( vpblendd)。相关:预测现代超标量处理器上的操作延迟需要考虑哪些因素以及如何手动计算它们?