上交所的排列并不容易。有多种方法可以通过不同的指令组合来实现相同的结果。不同的组合可能需要不同数量的指令、寄存器或存储器访问。我不想手动解决这样的难题,我更喜欢看看 LLVM 编译器做了什么,所以我用 LLVM 的中间语言编写了您想要的排列的简单版本,它利用了极其灵活的向量洗牌指令:
define void @shuffle_even_odd(<8 x i16>* %src0) {
%src1 = getelementptr <8 x i16>* %src0, i64 1
%a = load <8 x i16>* %src0, align 16
%b = load <8 x i16>* %src1, align 16
%x = shufflevector <8 x i16> %a, <8 x i16> %b, <8 x i32> <i32 1, i32 3, i32 5, i32 7, i32 9, i32 11, i32 13, i32 15>
%y = shufflevector <8 x i16> %a, <8 x i16> %b, <8 x i32> <i32 0, i32 2, i32 4, i32 6, i32 8, i32 10, i32 12, i32 14>
store <8 x i16> %x, <8 x i16>* %src0, align 16
store <8 x i16> %y, <8 x i16>* %src1, align 16
ret void
}
使用 LLVM IR-to-ASM 编译器对其进行编译:llc shuffle_even_odd.ll -o shuffle_even_odd.s
你会得到类似以下 x86 程序集的内容:
movdqa (%rdi), %xmm0
movdqa 16(%rdi), %xmm1
movdqa %xmm1, %xmm2
pshufb LCPI0_0(%rip), %xmm2
movdqa %xmm0, %xmm3
pshufb LCPI0_1(%rip), %xmm3
por %xmm2, %xmm3
movdqa %xmm3, (%rdi)
pshufb LCPI0_2(%rip), %xmm1
pshufb LCPI0_3(%rip), %xmm0
por %xmm1, %xmm0
movdqa %xmm0, 16(%rdi)
我排除了上面 LCPIO_* 引用的常量数据部分,但这大致翻译为以下 C 代码:
void shuffle_even_odd(__m128i * src) {
__m128i shuffle0 = _mm_setr_epi8(128, 128, 128, 128, 128, 128, 128, 128, 2, 3, 6, 7, 10, 11, 14, 15);
__m128i shuffle1 = _mm_setr_epi8(2, 3, 6, 7, 10, 11, 14, 15, 128, 128, 128, 128, 128, 128, 128, 128);
__m128i shuffle2 = _mm_setr_epi8(128, 128, 128, 128, 128, 128, 128, 128, 0, 1, 4, 5, 8, 9, 12, 13);
__m128i shuffle3 = _mm_setr_epi8(0, 1, 4, 5, 8, 9, 12, 13, 128, 128, 128, 128, 128, 128, 128, 128);
__m128i a = src[0];
__m128i b = src[1];
src[0] = _mm_or_si128(_mm_shuffle_epi8(b, shuffle0), _mm_shuffle_epi8(a, shuffle1));
src[1] = _mm_or_si128(_mm_shuffle_epi8(b, shuffle2), _mm_shuffle_epi8(a, shuffle3));
}
这只是 4 个随机播放和 2 个按位或指令。我怀疑这些按位指令可以比您建议的解包指令更有效地在 CPU 管道中进行调度。
您可以在 LLVM 下载页面的“Clang Binaries”包中找到“llc”编译器:http://www.llvm.org/releases/download.html http://www.llvm.org/releases/download.html