Initial tryout on double x 4 in MUDA

by syoyo

English version at MUDA dev blog

Intel AVX の 256 ビット SIMD(double x 4) の準備として、MUDA でも double x 4 をサポートするように機能追加しています.

とりえあえず dvec という型を導入して double x4 型を表すようにしました.


// input.mu
dvec bora_func(dvec a)
{
  return a * a * a;
}

まずは、実装が簡単だという理由で、今までの float x 4 と同じような形式でコード変換をするようにしてみました.
SSE バックエンドにかけるとこんな感じ.


$ mudah input.mu > bora.c
dvec bora (const double * a)
{
  const __muda_m256 t_dvec2 = (*((__muda_m256 *)(a))) ;
  const __muda_m256 t_dvec1 = (*((__muda_m256 *)(a))) ;
  const __muda_m256 t_dvec3  =  _muda_mul_4d( t_dvec2 ,  t_dvec1  ) ;
  const __muda_m256 t_dvec4 = (*((__muda_m256 *)(a))) ;
  const __muda_m256 t_dvec5  =  _muda_mul_4d( t_dvec3 ,  t_dvec4  ) ;
  return t_dvec5 ;
}

ここで、 __muda_m256_muda_mul_4d は、double x 4 を既存の SSE CPU でサポートするための簡単なラップです.


typedef union {
  struct { __m128d v[2]; };
  double f[4];
} __muda_m256 __attribute__((aligned(16)));

static inline __muda_m256 _muda_mul_4d(__muda_m256 a, __muda_m256 b)
{
  __muda_m256 ret;

  ret.v[0] = _mm_mul_pd(a.v[0], b.v[0]);
  ret.v[1] = _mm_mul_pd(a.v[1], b.v[1]);

  return ret;
}

さて、このコードを gcc(apple 4.0.1) にかけてみたところ…


$ gcc -msse2 -O3 -c bora.c
$ otool -v -t bora.o
_bora:
00000000        pushl   %ebp
00000001        movl    %esp,%ebp
00000003        pushl   %edi
00000004        pushl   %esi
00000005        subl    $0x00000150,%esp
0000000b        movl    0x0c(%ebp),%eax
0000000e        movl    (%eax),%edx
00000010        movl    %edx,0xfffffed4(%ebp)
...
00000111        movl    0xfffffec4(%ebp),%eax
00000117        mulpd   0xffffff38(%ebp),%xmm0
0000011f        movapd  %xmm0,0xffffff18(%ebp)
00000127        movapd  0xffffff08(%ebp),%xmm0
...
(total 170 instructions)

なんじゃこりゃーーー! mov* ばっかり! 6 muls のために 170 インストラクションってなんだそりゃ.

最新の llvm-gcc(llvm-gcc4.2-2.2-x86-darwin8) でコンパイルしても、まだまだ余分な mov がありました.


_bora:
00000000        pushl   %ebp
00000001        movl    %esp,%ebp
00000003        subl    $0x000000e8,%esp
00000009        movl    0x0c(%ebp),%eax
0000000c        movapd  0x10(%eax),%xmm0
00000011        movapd  (%eax),%xmm1
00000015        movapd  %xmm0,0xffffff68(%ebp)
0000001d        movapd  %xmm1,0xffffff58(%ebp)
00000025        movapd  %xmm0,0xffffff48(%ebp)
0000002d        movapd  %xmm1,0xffffff38(%ebp)
00000035        movapd  0xffffff58(%ebp),%xmm2
0000003d        mulpd   0xffffff38(%ebp),%xmm2
00000045        movapd  %xmm2,0xffffff78(%ebp)
0000004d        movapd  0xffffff68(%ebp),%xmm2
00000055        mulpd   0xffffff48(%ebp),%xmm2
0000005d        movapd  %xmm2,0x88(%ebp)
00000062        movapd  0xffffff78(%ebp),%xmm3
0000006a        movapd  %xmm2,0xc8(%ebp)
0000006f        movapd  %xmm3,0xb8(%ebp)
00000074        movapd  %xmm0,0xa8(%ebp)
00000079        movapd  %xmm1,0x98(%ebp)
0000007e        movapd  0xb8(%ebp),%xmm0
00000083        mulpd   0x98(%ebp),%xmm0
00000088        movapd  %xmm0,0xd8(%ebp)
0000008d        movapd  0xc8(%ebp),%xmm0
00000092        mulpd   0xa8(%ebp),%xmm0
00000097        movapd  %xmm0,0xe8(%ebp)
0000009c        movapd  0xd8(%ebp),%xmm1
000000a1        movapd  %xmm0,0xffffff28(%ebp)
000000a9        movapd  %xmm1,0xffffff18(%ebp)
000000b1        movl    0x08(%ebp),%eax
000000b4        movapd  0xffffff28(%ebp),%xmm0
000000bc        movapd  0xffffff18(%ebp),%xmm1
000000c4        movapd  %xmm1,(%eax)
000000c8        movapd  %xmm0,0x10(%eax)
000000cd        addl    $0x000000e8,%esp
000000d3        popl    %ebp
000000d4        ret     $0x0004
(38 instructions)

icc(intel c compiler) でも似たような結果でした.
構造体のアクセスが絡むと、C コンパイラとしては最適化しづらいのかもしれません.
最新のコンパイラだったらこの程度なら十分最適化すっしょ、と思っていましたが、
そうではなかったようです.
ちょっと面倒ですが、ベタに _mm_mul_pd() x 2 回とかになるようにコードを生成したほうがよさそうです.
(こういう知見を積み重ねて、MUDA で書けばほぼ最適化アセンブリになる C コードを吐くようにしたい)

LLVM IR ではどうだろうか?

次に、double x 4 を MUDA の LLVM バックエンドにも実装してみました.


$ mudah --llvm  input.mu
define  @bora ( %a)
{
  %a.addr = alloca  ;
  store  %a, * %a.addr ;
  %t_dvec2 = load * %a.addr ;

  %t_dvec1 = load * %a.addr ;

  %t_dvec3  =  mul  %t_dvec2 ,  %t_dvec1   ;
  %t_dvec4 = load * %a.addr ;

  %t_dvec5  =  mul  %t_dvec3 ,  %t_dvec4   ;
  ret  %t_dvec5 ;
}

$ llvm-as bora.ll -f
$ llc bora.bc -f
$ cat bora.s

_bora:
Leh_func_begin3:
Llabel3:
subl    $44, %esp
movapd    %xmm0, (%esp)
movapd    %xmm1, 16(%esp)
movaps    %xmm1, %xmm2
mulpd    %xmm2, %xmm2
mulpd    %xmm2, %xmm1
movaps    %xmm0, %xmm2
mulpd    %xmm2, %xmm2
mulpd    %xmm2, %xmm0
addl    $44, %esp
ret
(11 instructions)

いいですねー LLVM、ほぼ期待通りの出力です.
LLVM IR はベクトル型がネイティブでサポートされているので、最適化しやすいのでしょう.

下手に頑張って最適なアセンブリになるような C コードを try-and-error で見つけるよりは,
LLVM を使うようにしたほうがお手軽なのにほぼ最適化されているので、もう LLVM でいいような気がしてきました.

Advertisements