我正在处理一个数据结构,其中我有一个16 uint64的数组。它们在内存中布局如下(每个下面代表一个int64):
A0 A1 A2 A3 B0 B1 B2 B3 C0 C1 C2 C3 D0 D1 D2 D3
期望的结果是将数组转换为:
A0 B0 C0 D0 A1 B1 C1 D1 A2 B2 C2 D2 A3 B3 C3 D3
阵列旋转90度也是未来循环的可接受解决方案:
D0 C0 B0 A0 D1 C1 B1 A1 D2 C2 B2 A2 D3 C3 B3 A3
我需要这个以便稍后快速操作箭头(按顺序遍历另一个SIMD行程,每次4次)。
到目前为止,我已经尝试过"混合"数据通过加载A的4 x 64位向量,对这些元素进行位掩码和混洗,并将其与B&等进行“或”运算,然后对C进行重复...不幸的是,这是阵列中每个4个元素段的5 x 4 SIMD指令(一个加载,一个掩码,一个shuffle,一个或具有下一个元素,最后是一个存储)。看来我应该能做得更好。
我有AVX2可用,我用clang编译。
答案 0 :(得分:10)
uint64_t A[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
__m256i row0 = _mm256_loadu_si256((__m256i*)&A[ 0]); //0 1 2 3
__m256i row1 = _mm256_loadu_si256((__m256i*)&A[ 4]); //4 5 6 7
__m256i row2 = _mm256_loadu_si256((__m256i*)&A[ 8]); //8 9 a b
__m256i row3 = _mm256_loadu_si256((__m256i*)&A[12]); //c d e f
我现在没有硬件来测试这个,但是类似下面的东西应该做你想做的事情
__m256i tmp3, tmp2, tmp1, tmp0;
tmp0 = _mm256_unpacklo_epi64(row0, row1); //0 4 2 6
tmp1 = _mm256_unpackhi_epi64(row0, row1); //1 5 3 7
tmp2 = _mm256_unpacklo_epi64(row2, row3); //8 c a e
tmp3 = _mm256_unpackhi_epi64(row2, row3); //9 d b f
//now select the appropriate 128-bit lanes
row0 = _mm256_permute2x128_si256(tmp0, tmp2, 0x20); //0 4 8 c
row1 = _mm256_permute2x128_si256(tmp1, tmp3, 0x20); //1 5 9 d
row2 = _mm256_permute2x128_si256(tmp0, tmp2, 0x31); //2 6 a e
row3 = _mm256_permute2x128_si256(tmp1, tmp3, 0x31); //3 7 b f
在
__m256i _mm256_permute2x128_si256 (__m256i a, __m256i b, const int imm)
内在选择来自两个来源的128位通道。您可以在the Intel Intrinsic Guide中了解相关信息。有一个版本_mm256_permute2f128_si256
只需要AVX并在浮点域中起作用。我用它来检查我是否使用了正确的控制字。
答案 1 :(得分:4)
另一种方法是使用 gather 指令,您可以直接加载转置矩阵。下面的五行代码可以在i7-Haswell上使用gcc。
int32_t stride = 4 * sizeof(A[0]);
__m128i r128_gather_idx = _mm_set_epi32(3 * stride, 2 * stride, 1 * stride, 0 * stride);
__m256i row0 = _mm256_i32gather_epi64(reinterpret_cast<long long const *>(&A[ 0]), r128_gather_idx, 1);
__m256i row1 = _mm256_i32gather_epi64(reinterpret_cast<long long const *>(&A[ 1]), r128_gather_idx, 1);
__m256i row2 = _mm256_i32gather_epi64(reinterpret_cast<long long const *>(&A[ 2]), r128_gather_idx, 1);