12月
28
1つのベクタレジスタ(たとえば __m128i のこと)を返すラムダはきれいにインライン展開される(つまり、妙な一時変数がメモリ上に取られたりしない)ことは分かっているんですが、複数のベクタレジスタを返す普通の(=ラムダではない)関数がインライン展開されるときはどうなのか、というのが気になりました。
というわけでこんなコード。
static std::tuple<__m128i , __m128i, __m256i> addvec(__m128i a, __m128i b, __m128i c, __m128i d, __m256i e, __m256i f) { return std::make_tuple(_mm_add_epi8(a, b), _mm_add_epi8(c, d), _mm256_add_epi8(e, f)); } void foo(__m256i* res, __m128i* a, __m128i* b, __m128i* c, __m128i* d, __m256i* e, __m256i* f) { auto x = addvec(*a, *b, *c, *d, *e, *f); *res = _mm256_add_epi16(_mm256_castsi128_si256(_mm_add_epi16(std::get<0>(x), std::get<1>(x))), std::get<2>(x)); }
まず Linux の GCC 4.8.5 …だと何やら mangling がイケてないのか __m256i を使うとコンパイルエラーになります。 __m128i だけなら通ってきれいな展開結果が出力されます(結果は省略)
次に Mac の Xcode 8.3.2 の clang。
[umezawa@metis:ttys002 ~]$ clang++ -std=c++11 -mavx2 -O2 -S -o- vecinline.cc (略) __Z3fooPDv4_xPDv2_xS2_S2_S2_S0_S0_: ## @_Z3fooPDv4_xPDv2_xS2_S2_S2_S0_S0_ .cfi_startproc ## BB#0: pushq %rbp Ltmp0: .cfi_def_cfa_offset 16 Ltmp1: .cfi_offset %rbp, -16 movq %rsp, %rbp Ltmp2: .cfi_def_cfa_register %rbp movq 16(%rbp), %rax vmovdqa (%rdx), %xmm0 vmovdqa (%r8), %xmm1 vmovdqu (%rax), %ymm2 vpaddb (%rsi), %xmm0, %xmm0 vpaddb (%rcx), %xmm1, %xmm1 vpaddb (%r9), %ymm2, %ymm2 vpaddw %xmm0, %xmm1, %xmm0 vpaddw %ymm2, %ymm0, %ymm0 vmovdqu %ymm0, (%rdi) popq %rbp vzeroupper retq .cfi_endproc
これもきれいに展開されています。
でもって Cygwin の GCC 5.0.0。Windows だと呼び出し規約の関係でメモリアクセスが増えてしまって分かりにくいですが、妙な一時変数は出現せずちゃんとレジスタだけで計算されています。
$ gcc -mavx2 -O2 -o- -std=c++11 -S vecinline.cpp (中略) movq 40(%rsp), %rax vmovdqa (%r9), %xmm0 vpaddb (%rax), %xmm0, %xmm1 movq 48(%rsp), %rax vmovdqa (%rdx), %xmm0 vpaddb (%r8), %xmm0, %xmm0 vpaddw %xmm0, %xmm1, %xmm0 vmovdqa (%rax), %ymm1 movq 56(%rsp), %rax vpaddb (%rax), %ymm1, %ymm1 vpaddw %ymm0, %ymm1, %ymm0 vmovdqa %ymm0, (%rcx) vzeroupper ret (以下略)
で、懸案の Visual C++ 2015 ですが…
c:\cygwin64\home\umezawa>cl /O2 /c /arch:AVX2 /EHsc vecinline.cpp (略) $ objdump -d vecinline.obj vecinline.obj: ファイル形式 pe-x86-64 (略) セクション .text$mn の逆アセンブル: 0000000000000000 < ?foo@@YAXPEAT__m256i@@PEAT__m128i@@11100@Z>: 0: 40 55 rex push %rbp 2: 48 83 ec 20 sub $0x20,%rsp 6: 48 8d 6c 24 20 lea 0x20(%rsp),%rbp b: 48 83 e5 e0 and $0xffffffffffffffe0,%rbp f: c4 c1 7a 6f 09 vmovdqu (%r9),%xmm1 14: 48 8b 44 24 58 mov 0x58(%rsp),%rax 19: c5 fe 6f 00 vmovdqu (%rax),%ymm0 1d: 48 8b 44 24 60 mov 0x60(%rsp),%rax 22: c5 fd fc 18 vpaddb (%rax),%ymm0,%ymm3 26: c5 fa 6f 02 vmovdqu (%rdx),%xmm0 2a: 48 8b 44 24 50 mov 0x50(%rsp),%rax 2f: c5 f1 fc 10 vpaddb (%rax),%xmm1,%xmm2 33: c4 c1 79 fc 08 vpaddb (%r8),%xmm0,%xmm1 38: c5 f1 fd c2 vpaddw %xmm2,%xmm1,%xmm0 3c: c5 fd fd d3 vpaddw %ymm3,%ymm0,%ymm2 40: c5 fe 7f 11 vmovdqu %ymm2,(%rcx) 44: c5 f8 77 vzeroupper 47: 48 83 c4 20 add $0x20,%rsp 4b: 5d pop %rbp 4c: c3 retq
プロローグとエピローグになんかくっついているのが気になりますが、肝心の展開結果に関しては期待通りになっています。
これが Visual C++ 2017 になるとプロローグとエピローグがきれいになって、
0000000000000000 < ?foo@@YAXPEAT__m256i@@PEAT__m128i@@11100@Z>: 0: c4 c1 7a 6f 09 vmovdqu (%r9),%xmm1 5: 48 8b 44 24 30 mov 0x30(%rsp),%rax a: c5 fe 6f 00 vmovdqu (%rax),%ymm0 e: 48 8b 44 24 38 mov 0x38(%rsp),%rax 13: c5 fd fc 18 vpaddb (%rax),%ymm0,%ymm3 17: c5 fa 6f 02 vmovdqu (%rdx),%xmm0 1b: 48 8b 44 24 28 mov 0x28(%rsp),%rax 20: c5 f1 fc 10 vpaddb (%rax),%xmm1,%xmm2 24: c4 c1 79 fc 08 vpaddb (%r8),%xmm0,%xmm1 29: c5 f1 fd c2 vpaddw %xmm2,%xmm1,%xmm0 2d: c5 fd fd d3 vpaddw %ymm3,%ymm0,%ymm2 31: c5 fe 7f 11 vmovdqu %ymm2,(%rcx) 35: c5 f8 77 vzeroupper 38: c3 retq
こうなります。
結論として、どのコンパイラでも期待通りにきれいな展開結果になるので、少なくとも簡単な関数であれば安心して使っていいということになります。
no comment untill now