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

こうなります。

結論として、どのコンパイラでも期待通りにきれいな展開結果になるので、少なくとも簡単な関数であれば安心して使っていいということになります。

Trackback

no comment untill now

Add your comment now