6月
11
その2の続き
2要素の場合で既にかなり残念なことになっていますが、3要素の場合はどうでしょうか。
template<typename T> void foo(T* __restrict dst, const T* __restrict a, const T* __restrict b, const T* __restrict c, size_t n) { for (size_t i = 0; i < n; ++i) { dst[0] = *a++; dst[1] = *b++; dst[2] = *c++; dst += 3; } } template void foo<char>(char* __restrict, const char* __restrict, const char* __restrict, const char* __restrict, size_t);
3要素の場合は自明な最適解はないと思うのですが、UtVideo では以下のようなコードを書いています。
lddqu xmm0, [rsi+rcx] lddqu xmm1, [rdx+rcx] lddqu xmm2, [rbx+rcx] palignr xmm1, xmm1, 5 palignr xmm2, xmm2, 11 movdqa xmm5, xmm2 palignr xmm2, xmm0, 11 palignr xmm0, xmm1, 11 palignr xmm1, xmm5, 11 movdqa xmm5, xmm2 palignr xmm2, xmm1, 10 palignr xmm1, xmm0, 10 palignr xmm0, xmm5, 10 pshufb xmm0, xmm7 pshufb xmm1, xmm7 pshufb xmm2, xmm7 movdqu [rdi ], xmm0 movdqu [rdi+16], xmm1 movdqu [rdi+32], xmm2
2要素でダメなものが3要素でできるわけがないので、Visual C++ (の cl.exe)と Clang/C2 はほっといて wandbox 上で GCC と Clang で試してみました。まず Clang は 4.0.0 でも HEAD でもダメで、 GCC だと 5.1.0 以降で以下のようなコードを吐いてくれます。
.L5: movdqa xmm0, xmm12 add rbx, 1 add r11, 48 movdqu xmm3, XMMWORD PTR [rsi+r10] movdqu xmm1, XMMWORD PTR [rdx+r10] movdqa xmm4, xmm3 movdqu xmm2, XMMWORD PTR [rcx+r10] add r10, 16 punpcklbw xmm4, xmm1 movdqa xmm15, xmm2 pshufb xmm4, xmm14 pshufb xmm15, xmm13 pblendvb xmm4, xmm15, xmm0 movdqa xmm0, xmm1 movdqa xmm15, xmm2 movups XMMWORD PTR [r11-64], xmm4 movdqa xmm4, xmm3 pshufb xmm0, xmm10 pshufb xmm4, xmm11 pshufb xmm3, xmm7 pshufb xmm1, xmm6 por xmm4, xmm0 movdqa xmm0, xmm8 pshufb xmm15, xmm9 por xmm1, xmm3 pshufb xmm2, xmm5 pblendvb xmm4, xmm15, xmm0 movdqa xmm0, XMMWORD PTR .LC10[rip] pblendvb xmm1, xmm2, xmm0 movups XMMWORD PTR [r11-48], xmm4 movups XMMWORD PTR [r11-32], xmm1 cmp r9, rbx ja .L5
これ要するに、a, b, c のそれぞれから pshufb で要素を必要な位置に持ってきた後、por や pblendvb で合成する、ということのようです。x64 でレジスタが多いので pshufb のインデックスや pblendvb のセレクタはほぼ全部レジスタに乗っていますが、x86 だとレジスタが少ないのでかなり辛いことになりそうです。
命令の数や種類を見る限りでは私が手書きしたものの方が速そうです。やっていることは単純でもそのものズバリの命令がない例ですが、そういうものこそコンパイラが謎の技術で高速なコードを出力してほしい所です。
その4に続くかもしれない
no comment untill now