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