一个更好的8×8字节矩阵转置与SSE?

我发现这篇文章解释了如何通过24次操作转置8×8字节矩阵,稍后会有几个滚动条实现转置。 但是,这种方法没有利用我们可以阻止 8×8转置为4个4×4转置的事实,并且每个转换只能在一个shuffle指令中完成( 这篇文章是参考文献)。 所以我推出了这个解决方案:

__m128i transpose4x4mask = _mm_set_epi8(15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0); __m128i shuffle8x8Mask = _mm_setr_epi8(0, 1, 2, 3, 8, 9, 10, 11, 4, 5, 6, 7, 12, 13, 14, 15); void TransposeBlock8x8(uint8_t *src, uint8_t *dst, int srcStride, int dstStride) { __m128i load0 = _mm_set_epi64x(*(uint64_t*)(src + 1 * srcStride), *(uint64_t*)(src + 0 * srcStride)); __m128i load1 = _mm_set_epi64x(*(uint64_t*)(src + 3 * srcStride), *(uint64_t*)(src + 2 * srcStride)); __m128i load2 = _mm_set_epi64x(*(uint64_t*)(src + 5 * srcStride), *(uint64_t*)(src + 4 * srcStride)); __m128i load3 = _mm_set_epi64x(*(uint64_t*)(src + 7 * srcStride), *(uint64_t*)(src + 6 * srcStride)); __m128i shuffle0 = _mm_shuffle_epi8(load0, shuffle8x8Mask); __m128i shuffle1 = _mm_shuffle_epi8(load1, shuffle8x8Mask); __m128i shuffle2 = _mm_shuffle_epi8(load2, shuffle8x8Mask); __m128i shuffle3 = _mm_shuffle_epi8(load3, shuffle8x8Mask); __m128i block0 = _mm_unpacklo_epi64(shuffle0, shuffle1); __m128i block1 = _mm_unpackhi_epi64(shuffle0, shuffle1); __m128i block2 = _mm_unpacklo_epi64(shuffle2, shuffle3); __m128i block3 = _mm_unpackhi_epi64(shuffle2, shuffle3); __m128i transposed0 = _mm_shuffle_epi8(block0, transpose4x4mask); __m128i transposed1 = _mm_shuffle_epi8(block1, transpose4x4mask); __m128i transposed2 = _mm_shuffle_epi8(block2, transpose4x4mask); __m128i transposed3 = _mm_shuffle_epi8(block3, transpose4x4mask); __m128i store0 = _mm_unpacklo_epi32(transposed0, transposed2); __m128i store1 = _mm_unpackhi_epi32(transposed0, transposed2); __m128i store2 = _mm_unpacklo_epi32(transposed1, transposed3); __m128i store3 = _mm_unpackhi_epi32(transposed1, transposed3); *((uint64_t*)(dst + 0 * dstStride)) = _mm_extract_epi64(store0, 0); *((uint64_t*)(dst + 1 * dstStride)) = _mm_extract_epi64(store0, 1); *((uint64_t*)(dst + 2 * dstStride)) = _mm_extract_epi64(store1, 0); *((uint64_t*)(dst + 3 * dstStride)) = _mm_extract_epi64(store1, 1); *((uint64_t*)(dst + 4 * dstStride)) = _mm_extract_epi64(store2, 0); *((uint64_t*)(dst + 5 * dstStride)) = _mm_extract_epi64(store2, 1); *((uint64_t*)(dst + 6 * dstStride)) = _mm_extract_epi64(store3, 0); *((uint64_t*)(dst + 7 * dstStride)) = _mm_extract_epi64(store3, 1); } 

排除加载/存储操作,此过程仅包含16条指令而不是24条指令。

我错过了什么?

除了可以读取和写入内存的加载,存储和pinsrq -s,可能只有8个字节的步幅,你可以只用12条指令进行转置(这个代码可以很容易地与Z boson的测试结合使用)码):

 void tran8x8b_SSE_v2(char *A, char *B) { __m128i pshufbcnst = _mm_set_epi8(15,11,7,3, 14,10,6,2, 13,9,5,1, 12,8,4,0); __m128i B0, B1, B2, B3, T0, T1, T2, T3; B0 = _mm_loadu_si128((__m128i*)&A[ 0]); B1 = _mm_loadu_si128((__m128i*)&A[16]); B2 = _mm_loadu_si128((__m128i*)&A[32]); B3 = _mm_loadu_si128((__m128i*)&A[48]); T0 = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(B0),_mm_castsi128_ps(B1),0b10001000)); T1 = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(B2),_mm_castsi128_ps(B3),0b10001000)); T2 = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(B0),_mm_castsi128_ps(B1),0b11011101)); T3 = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(B2),_mm_castsi128_ps(B3),0b11011101)); B0 = _mm_shuffle_epi8(T0,pshufbcnst); B1 = _mm_shuffle_epi8(T1,pshufbcnst); B2 = _mm_shuffle_epi8(T2,pshufbcnst); B3 = _mm_shuffle_epi8(T3,pshufbcnst); T0 = _mm_unpacklo_epi32(B0,B1); T1 = _mm_unpackhi_epi32(B0,B1); T2 = _mm_unpacklo_epi32(B2,B3); T3 = _mm_unpackhi_epi32(B2,B3); _mm_storeu_si128((__m128i*)&B[ 0], T0); _mm_storeu_si128((__m128i*)&B[16], T1); _mm_storeu_si128((__m128i*)&B[32], T2); _mm_storeu_si128((__m128i*)&B[48], T3); } 

这里我们使用32位浮点shuffle,它比epi32 shuffle更灵活。 强制转换不会生成额外的指令(使用gcc 5.4生成的代码):

 tran8x8b_SSE_v2: .LFB4885: .cfi_startproc vmovdqu 48(%rdi), %xmm5 vmovdqu 32(%rdi), %xmm2 vmovdqu 16(%rdi), %xmm0 vmovdqu (%rdi), %xmm1 vshufps $136, %xmm5, %xmm2, %xmm4 vshufps $221, %xmm5, %xmm2, %xmm2 vmovdqa .LC6(%rip), %xmm5 vshufps $136, %xmm0, %xmm1, %xmm3 vshufps $221, %xmm0, %xmm1, %xmm1 vpshufb %xmm5, %xmm3, %xmm3 vpshufb %xmm5, %xmm1, %xmm0 vpshufb %xmm5, %xmm4, %xmm4 vpshufb %xmm5, %xmm2, %xmm1 vpunpckldq %xmm4, %xmm3, %xmm5 vpunpckldq %xmm1, %xmm0, %xmm2 vpunpckhdq %xmm4, %xmm3, %xmm3 vpunpckhdq %xmm1, %xmm0, %xmm0 vmovups %xmm5, (%rsi) vmovups %xmm3, 16(%rsi) vmovups %xmm2, 32(%rsi) vmovups %xmm0, 48(%rsi) ret .cfi_endproc 

在某些(但不是全部)较旧的cpus上,可能存在一个小的旁路延迟(在0到2个周期之间),用于在整数和浮点单元之间移动数据。 这会增加函数的延迟,但不一定会影响代码的吞吐量。

使用1e9转换的简单延迟测试:

  for (int i=0;i<500000000;i++){ tran8x8b_SSE(A,C); tran8x8b_SSE(C,A); } print8x8b(A); 

使用tran8x8b_SSE需要大约5.5秒(19.7e9个周期),使用tran8x8b_SSE_v2(英特尔核心i5-6500)需要4.5秒(16.0e9个周期)。 请注意,虽然函数在for循环中内联,但编译器并未消除加载和存储。

更新:带混合的AVX2-128 / SSE 4.1解决方案。

'shuffles'(解包,shuffle)由端口5处理,在现代cpu上每个cpu周期有1条指令。 有时用两个混合物替换一个'shuffle'是值得的。 在Skylake上,32位混合指令可以在0,1或5端口上运行。

不幸的是, _mm_blend_epi32只是AVX2-128。 一个有效的SSE 4.1替代方案是_mm_blend_ps与几个演员(通常是免费的)组合。 12'洗牌'被8次洗牌和8次混合所取代。

简单的延迟测试现在运行大约3.6秒(13e9 cpu周期),比tran8x8b_SSE_v2的结果快18%。

码:

 /* AVX2-128 version, sse 4.1 version see ----------------> SSE 4.1 version of tran8x8b_AVX2_128() */ void tran8x8b_AVX2_128(char *A, char *B) { /* void tran8x8b_SSE4_1(char *A, char *B) { */ __m128i pshufbcnst_0 = _mm_set_epi8(15, 7,11, 3, 13, 5, 9, 1, 14, 6,10, 2, 12, 4, 8, 0); /* __m128i pshufbcnst_0 = _mm_set_epi8(15, 7,11, 3, 13, 5, 9, 1, 14, 6,10, 2, 12, 4, 8, 0); */ __m128i pshufbcnst_1 = _mm_set_epi8(13, 5, 9, 1, 15, 7,11, 3, 12, 4, 8, 0, 14, 6,10, 2); /* __m128i pshufbcnst_1 = _mm_set_epi8(13, 5, 9, 1, 15, 7,11, 3, 12, 4, 8, 0, 14, 6,10, 2); */ __m128i pshufbcnst_2 = _mm_set_epi8(11, 3,15, 7, 9, 1,13, 5, 10, 2,14, 6, 8, 0,12, 4); /* __m128i pshufbcnst_2 = _mm_set_epi8(11, 3,15, 7, 9, 1,13, 5, 10, 2,14, 6, 8, 0,12, 4); */ __m128i pshufbcnst_3 = _mm_set_epi8( 9, 1,13, 5, 11, 3,15, 7, 8, 0,12, 4, 10, 2,14, 6); /* __m128i pshufbcnst_3 = _mm_set_epi8( 9, 1,13, 5, 11, 3,15, 7, 8, 0,12, 4, 10, 2,14, 6); */ __m128i B0, B1, B2, B3, T0, T1, T2, T3; /* __m128 B0, B1, B2, B3, T0, T1, T2, T3; */ /* */ B0 = _mm_loadu_si128((__m128i*)&A[ 0]); /* B0 = _mm_loadu_ps((float*)&A[ 0]); */ B1 = _mm_loadu_si128((__m128i*)&A[16]); /* B1 = _mm_loadu_ps((float*)&A[16]); */ B2 = _mm_loadu_si128((__m128i*)&A[32]); /* B2 = _mm_loadu_ps((float*)&A[32]); */ B3 = _mm_loadu_si128((__m128i*)&A[48]); /* B3 = _mm_loadu_ps((float*)&A[48]); */ /* */ B1 = _mm_shuffle_epi32(B1,0b10110001); /* B1 = _mm_shuffle_ps(B1,B1,0b10110001); */ B3 = _mm_shuffle_epi32(B3,0b10110001); /* B3 = _mm_shuffle_ps(B3,B3,0b10110001); */ T0 = _mm_blend_epi32(B0,B1,0b1010); /* T0 = _mm_blend_ps(B0,B1,0b1010); */ T1 = _mm_blend_epi32(B2,B3,0b1010); /* T1 = _mm_blend_ps(B2,B3,0b1010); */ T2 = _mm_blend_epi32(B0,B1,0b0101); /* T2 = _mm_blend_ps(B0,B1,0b0101); */ T3 = _mm_blend_epi32(B2,B3,0b0101); /* T3 = _mm_blend_ps(B2,B3,0b0101); */ /* */ B0 = _mm_shuffle_epi8(T0,pshufbcnst_0); /* B0 = _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(T0),pshufbcnst_0)); */ B1 = _mm_shuffle_epi8(T1,pshufbcnst_1); /* B1 = _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(T1),pshufbcnst_1)); */ B2 = _mm_shuffle_epi8(T2,pshufbcnst_2); /* B2 = _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(T2),pshufbcnst_2)); */ B3 = _mm_shuffle_epi8(T3,pshufbcnst_3); /* B3 = _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(T3),pshufbcnst_3)); */ /* */ T0 = _mm_blend_epi32(B0,B1,0b1010); /* T0 = _mm_blend_ps(B0,B1,0b1010); */ T1 = _mm_blend_epi32(B0,B1,0b0101); /* T1 = _mm_blend_ps(B0,B1,0b0101); */ T2 = _mm_blend_epi32(B2,B3,0b1010); /* T2 = _mm_blend_ps(B2,B3,0b1010); */ T3 = _mm_blend_epi32(B2,B3,0b0101); /* T3 = _mm_blend_ps(B2,B3,0b0101); */ T1 = _mm_shuffle_epi32(T1,0b10110001); /* T1 = _mm_shuffle_ps(T1,T1,0b10110001); */ T3 = _mm_shuffle_epi32(T3,0b10110001); /* T3 = _mm_shuffle_ps(T3,T3,0b10110001); */ /* */ _mm_storeu_si128((__m128i*)&B[ 0], T0); /* _mm_storeu_ps((float*)&B[ 0], T0); */ _mm_storeu_si128((__m128i*)&B[16], T1); /* _mm_storeu_ps((float*)&B[16], T1); */ _mm_storeu_si128((__m128i*)&B[32], T2); /* _mm_storeu_ps((float*)&B[32], T2); */ _mm_storeu_si128((__m128i*)&B[48], T3); /* _mm_storeu_ps((float*)&B[48], T3); */ } /* } */ 

将此作为答案发布。 由于到目前为止收到了一些答案和评论,我还要将问题的标题从“……与SSE”改为“……与SIMD”。

我成功地将AVX2矩阵转换为8个指令,10个包括加载/存储(不包括掩码加载)。 编辑:我发现了一个较短的版本。 见下文。 这是矩阵在存储器中都是连续的情况,因此可以使用直接加载/存储。

这是C代码:

 void tran8x8b_AVX2(char *src, char *dst) { __m256i perm = _mm256_set_epi8( 0, 0, 0, 7, 0, 0, 0, 5, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 6, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 0 ); __m256i tm = _mm256_set_epi8( 15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0, 15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0 ); __m256i load0 = _mm256_loadu_si256((__m256i*)&src[ 0]); __m256i load1 = _mm256_loadu_si256((__m256i*)&src[32]); __m256i perm0 = _mm256_permutevar8x32_epi32(load0, perm); __m256i perm1 = _mm256_permutevar8x32_epi32(load1, perm); __m256i transpose0 = _mm256_shuffle_epi8(perm0, tm); __m256i transpose1 = _mm256_shuffle_epi8(perm1, tm); __m256i unpack0 = _mm256_unpacklo_epi32(transpose0, transpose1); __m256i unpack1 = _mm256_unpackhi_epi32(transpose0, transpose1); perm0 = _mm256_castps_si256(_mm256_permute2f128_ps(_mm256_castsi256_ps(unpack0), _mm256_castsi256_ps(unpack1), 32)); perm1 = _mm256_castps_si256(_mm256_permute2f128_ps(_mm256_castsi256_ps(unpack0), _mm256_castsi256_ps(unpack1), 49)); _mm256_storeu_si256((__m256i*)&dst[ 0], perm0); _mm256_storeu_si256((__m256i*)&dst[32], perm1); } 

GCC非常聪明,能够在AVX加载期间执行排列,从而节省了两条指令。 这是编译器输出:

 tran8x8b_AVX2(char*, char*): vmovdqa ymm1, YMMWORD PTR .LC0[rip] vmovdqa ymm2, YMMWORD PTR .LC1[rip] vpermd ymm0, ymm1, YMMWORD PTR [rdi] vpermd ymm1, ymm1, YMMWORD PTR [rdi+32] vpshufb ymm0, ymm0, ymm2 vpshufb ymm1, ymm1, ymm2 vpunpckldq ymm2, ymm0, ymm1 vpunpckhdq ymm0, ymm0, ymm1 vinsertf128 ymm1, ymm2, xmm0, 1 vperm2f128 ymm0, ymm2, ymm0, 49 vmovdqu YMMWORD PTR [rsi], ymm1 vmovdqu YMMWORD PTR [rsi+32], ymm0 vzeroupper ret 

它使用-O3发出vzerupper指令,但是转到vzerupper会删除它。

如果我的原始问题(一个大矩阵,我正在放大到它的8×8部分),处理步幅会以非常糟糕的方式破坏输出:

 void tran8x8b_AVX2(char *src, char *dst, int srcStride, int dstStride) { __m256i load0 = _mm256_set_epi64x(*(uint64_t*)(src + 3 * srcStride), *(uint64_t*)(src + 2 * srcStride), *(uint64_t*)(src + 1 * srcStride), *(uint64_t*)(src + 0 * srcStride)); __m256i load1 = _mm256_set_epi64x(*(uint64_t*)(src + 7 * srcStride), *(uint64_t*)(src + 6 * srcStride), *(uint64_t*)(src + 5 * srcStride), *(uint64_t*)(src + 4 * srcStride)); // ... the same as before, however we can skip the final permutations because we need to handle the destination stride... *((uint64_t*)(dst + 0 * dstStride)) = _mm256_extract_epi64(unpack0, 0); *((uint64_t*)(dst + 1 * dstStride)) = _mm256_extract_epi64(unpack0, 1); *((uint64_t*)(dst + 2 * dstStride)) = _mm256_extract_epi64(unpack1, 0); *((uint64_t*)(dst + 3 * dstStride)) = _mm256_extract_epi64(unpack1, 1); *((uint64_t*)(dst + 4 * dstStride)) = _mm256_extract_epi64(unpack0, 2); *((uint64_t*)(dst + 5 * dstStride)) = _mm256_extract_epi64(unpack0, 3); *((uint64_t*)(dst + 6 * dstStride)) = _mm256_extract_epi64(unpack1, 2); *((uint64_t*)(dst + 7 * dstStride)) = _mm256_extract_epi64(unpack1, 3); } 

这是编译器输出:

 tran8x8b_AVX2(char*, char*, int, int): movsx rdx, edx vmovq xmm5, QWORD PTR [rdi] lea r9, [rdi+rdx] vmovdqa ymm3, YMMWORD PTR .LC0[rip] movsx rcx, ecx lea r11, [r9+rdx] vpinsrq xmm0, xmm5, QWORD PTR [r9], 1 lea r10, [r11+rdx] vmovq xmm4, QWORD PTR [r11] vpinsrq xmm1, xmm4, QWORD PTR [r10], 1 lea r8, [r10+rdx] lea rax, [r8+rdx] vmovq xmm7, QWORD PTR [r8] vmovq xmm6, QWORD PTR [rax+rdx] vpinsrq xmm2, xmm7, QWORD PTR [rax], 1 vinserti128 ymm1, ymm0, xmm1, 0x1 vpinsrq xmm0, xmm6, QWORD PTR [rax+rdx*2], 1 lea rax, [rsi+rcx] vpermd ymm1, ymm3, ymm1 vinserti128 ymm0, ymm2, xmm0, 0x1 vmovdqa ymm2, YMMWORD PTR .LC1[rip] vpshufb ymm1, ymm1, ymm2 vpermd ymm0, ymm3, ymm0 vpshufb ymm0, ymm0, ymm2 vpunpckldq ymm2, ymm1, ymm0 vpunpckhdq ymm0, ymm1, ymm0 vmovdqa xmm1, xmm2 vmovq QWORD PTR [rsi], xmm1 vpextrq QWORD PTR [rax], xmm1, 1 vmovdqa xmm1, xmm0 add rax, rcx vextracti128 xmm0, ymm0, 0x1 vmovq QWORD PTR [rax], xmm1 add rax, rcx vpextrq QWORD PTR [rax], xmm1, 1 add rax, rcx vextracti128 xmm1, ymm2, 0x1 vmovq QWORD PTR [rax], xmm1 add rax, rcx vpextrq QWORD PTR [rax], xmm1, 1 vmovq QWORD PTR [rax+rcx], xmm0 vpextrq QWORD PTR [rax+rcx*2], xmm0, 1 vzeroupper ret 

但是,如果与输出原始代码进行比较,这似乎不是什么大问题。


编辑:我发现了一个较短的版本。 总共4条指令,8条指示加载/存储。 这是可能的,因为我以不同的方式读取矩阵,在加载期间在“gather”指令中隐藏了一些“shuffle”。 另外,请注意,执行存储需要最终的排列,因为AVX2没有“分散”指令。 使用分散指令只会将所有内容都归结为2条指令。 另外,请注意,通过更改vindex向量的内容,我可以毫不vindex处理src步幅。

不幸的是,这个AVX_v2似乎比前一个慢。 这是代码:

 void tran8x8b_AVX2_v2(char *src1, char *dst1) { __m256i tm = _mm256_set_epi8( 15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0, 15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0 ); __m256i vindex = _mm256_setr_epi32(0, 8, 16, 24, 32, 40, 48, 56); __m256i perm = _mm256_setr_epi32(0, 4, 1, 5, 2, 6, 3, 7); __m256i load0 = _mm256_i32gather_epi32((int*)src1, vindex, 1); __m256i load1 = _mm256_i32gather_epi32((int*)(src1 + 4), vindex, 1); __m256i transpose0 = _mm256_shuffle_epi8(load0, tm); __m256i transpose1 = _mm256_shuffle_epi8(load1, tm); __m256i final0 = _mm256_permutevar8x32_epi32(transpose0, perm); __m256i final1 = _mm256_permutevar8x32_epi32(transpose1, perm); _mm256_storeu_si256((__m256i*)&dst1[ 0], final0); _mm256_storeu_si256((__m256i*)&dst1[32], final1); } 

这是编译器的输出:

 tran8x8b_AVX2_v2(char*, char*): vpcmpeqd ymm3, ymm3, ymm3 vmovdqa ymm2, YMMWORD PTR .LC0[rip] vmovdqa ymm4, ymm3 vpgatherdd ymm0, DWORD PTR [rdi+4+ymm2*8], ymm3 vpgatherdd ymm1, DWORD PTR [rdi+ymm2*8], ymm4 vmovdqa ymm2, YMMWORD PTR .LC1[rip] vpshufb ymm1, ymm1, ymm2 vpshufb ymm0, ymm0, ymm2 vmovdqa ymm2, YMMWORD PTR .LC2[rip] vpermd ymm1, ymm2, ymm1 vpermd ymm0, ymm2, ymm0 vmovdqu YMMWORD PTR [rsi], ymm1 vmovdqu YMMWORD PTR [rsi+32], ymm0 vzeroupper ret 

通常,当加载和存储指令不计数时,因为代码正在使用寄存器中的矩阵,例如除了循环中的转置之外还执行多个操作。 在这种情况下,加载和存储不计算,因为它们不是主循环的一部分。

但是在你的代码中,加载和存储(或者更确切地说是设置和提取)正在进行转置的一部分。

GCC使用_mm_insert_epi64_mm_loadl_epi64在代码中为SSE4.1实现_mm_loadl_epi64 。 插入指令正在进行转置的一部分,即转置开始于load0,1,2,3而不是shuffle0,1,2,3 。 然后你的最终store0,1,2,3值也不包含转置。 您必须使用八个_mm_extract_epi64指令才能在内存中完成转置。 因此,不计算集合和提取内在函数是没有意义的。

在任何情况下,事实certificate你可以只用16条指令从寄存器进行转置,只使用SSSE3,如下所示:

 //__m128i B0, __m128i B1, __m128i B2, __m128i B3 __m128i mask = _mm_setr_epi8(0x0,0x04,0x01,0x05, 0x02,0x06,0x03,0x07, 0x08,0x0c,0x09,0x0d, 0x0a,0x0e,0x0b,0x0f); __m128i T0, T1, T2, T3; T0 = _mm_unpacklo_epi8(B0,B1); T1 = _mm_unpackhi_epi8(B0,B1); T2 = _mm_unpacklo_epi8(B2,B3); T3 = _mm_unpackhi_epi8(B2,B3); B0 = _mm_unpacklo_epi16(T0,T2); B1 = _mm_unpackhi_epi16(T0,T2); B2 = _mm_unpacklo_epi16(T1,T3); B3 = _mm_unpackhi_epi16(T1,T3); T0 = _mm_unpacklo_epi32(B0,B2); T1 = _mm_unpackhi_epi32(B0,B2); T2 = _mm_unpacklo_epi32(B1,B3); T3 = _mm_unpackhi_epi32(B1,B3); B0 = _mm_shuffle_epi8(T0,mask); B1 = _mm_shuffle_epi8(T1,mask); B2 = _mm_shuffle_epi8(T2,mask); B3 = _mm_shuffle_epi8(T3,mask); 

我不确定在这里排除负载和存储是否有意义,因为我不确定在4个128位寄存器中使用8×8字节矩阵是多么方便。

这是代码测试:

 #include  #include  void print8x8b(char *A) { for(int i=0; i<8; i++) { for(int j=0; j<8; j++) { printf("%2d ", A[i*8+j]); } puts(""); } puts(""); } void tran8x8b(char *A, char *B) { for(int i=0; i<8; i++) { for(int j=0; j<8; j++) { B[j*8+i] = A[i*8+j]; } } } void tran8x8b_SSE(char *A, char *B) { __m128i mask = _mm_setr_epi8(0x0,0x04,0x01,0x05, 0x02,0x06,0x03,0x07, 0x08,0x0c,0x09,0x0d, 0x0a,0x0e,0x0b,0x0f); __m128i B0, B1, B2, B3, T0, T1, T2, T3; B0 = _mm_loadu_si128((__m128i*)&A[ 0]); B1 = _mm_loadu_si128((__m128i*)&A[16]); B2 = _mm_loadu_si128((__m128i*)&A[32]); B3 = _mm_loadu_si128((__m128i*)&A[48]); T0 = _mm_unpacklo_epi8(B0,B1); T1 = _mm_unpackhi_epi8(B0,B1); T2 = _mm_unpacklo_epi8(B2,B3); T3 = _mm_unpackhi_epi8(B2,B3); B0 = _mm_unpacklo_epi16(T0,T2); B1 = _mm_unpackhi_epi16(T0,T2); B2 = _mm_unpacklo_epi16(T1,T3); B3 = _mm_unpackhi_epi16(T1,T3); T0 = _mm_unpacklo_epi32(B0,B2); T1 = _mm_unpackhi_epi32(B0,B2); T2 = _mm_unpacklo_epi32(B1,B3); T3 = _mm_unpackhi_epi32(B1,B3); B0 = _mm_shuffle_epi8(T0,mask); B1 = _mm_shuffle_epi8(T1,mask); B2 = _mm_shuffle_epi8(T2,mask); B3 = _mm_shuffle_epi8(T3,mask); _mm_storeu_si128((__m128i*)&B[ 0], B0); _mm_storeu_si128((__m128i*)&B[16], B1); _mm_storeu_si128((__m128i*)&B[32], B2); _mm_storeu_si128((__m128i*)&B[48], B3); } int main(void) { char A[64], B[64], C[64]; for(int i=0; i<64; i++) A[i] = i; print8x8b(A); tran8x8b(A,B); print8x8b(B); tran8x8b_SSE(A,C); print8x8b(C); } 

一个简化的

 void tp128_8x8(char *A, char *B) { __m128i sv = _mm_set_epi8(15, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 0); __m128i iv[4], ov[4]; ov[0] = _mm_shuffle_epi8(_mm_loadu_si128((__m128i*)A), sv); ov[1] = _mm_shuffle_epi8(_mm_loadu_si128((__m128i*)(A+16)), sv); ov[2] = _mm_shuffle_epi8(_mm_loadu_si128((__m128i*)(A+32)), sv); ov[3] = _mm_shuffle_epi8(_mm_loadu_si128((__m128i*)(A+48)), sv); iv[0] = _mm_unpacklo_epi16(ov[0], ov[1]); iv[1] = _mm_unpackhi_epi16(ov[0], ov[1]); iv[2] = _mm_unpacklo_epi16(ov[2], ov[3]); iv[3] = _mm_unpackhi_epi16(ov[2], ov[3]); _mm_storeu_si128((__m128i*)B, _mm_unpacklo_epi32(iv[0], iv[2])); _mm_storeu_si128((__m128i*)(B+16), _mm_unpackhi_epi32(iv[0], iv[2])); _mm_storeu_si128((__m128i*)(B+32), _mm_unpacklo_epi32(iv[1], iv[3])); _mm_storeu_si128((__m128i*)(B+48), _mm_unpackhi_epi32(iv[1], iv[3])); } Benchmark:i5-5300U 2.3GHz (cycles per byte) tran8x8b : 2.140 tran8x8b_SSE : 1.602 tran8x8b_SSE_v2 : 1.551 tp128_8x8 : 1.535 tran8x8b_AVX2 : 1.563 tran8x8b_AVX2_v2 : 1.731