GCCの勉強とmruby

最近ちょとmrubyのコードを見たりしていた。ついでにGCCの使い方を勉強しつつ、吐き出すアセンブラを眺めてみたり、そんな感じ。
勉強がてらVMまわりを少しいじってみたので自分用メモを残しておく。
眺めてたのはVMのコードで、mrubyのVMGCCではダイレクトスレッデッドコードになるので、ラベルの配列を作ってgotoすることで命令を実行していく。
gotoするときはNEXTというマクロを使う。

#define NEXT mrb->arena_idx = ai; i=*++pc; goto *optable[GET_OPCODE(i)]

ここで疑問なのはmrb->arena_idxで、こいつは何に使うのかというと、ここに説明が書いてある。
http://www.dzeta.jp/~junjis/code_reading/index.php?mruby%2FGC%BD%E8%CD%FD%A4%F2%C6%C9%A4%E0
ようするにオブジェクトを大量に生成するときに一時的にGC用にいれておく場所???
ということであれば、mrb->arena_idxはオブジェクトを生成するとき以外は戻す必要は無いんじゃないか。

ところでVM命令のひとつ、OP_MOVEのコードはこうなっている。コメントアウトされているところは削った。

    CASE(OP_MOVE) {
      /* A B    R(A) := R(B) */
      int a = GETARG_A(i);
      int b = GETARG_B(i);

      regs[a].tt = regs[b].tt;
      regs[a].value = regs[b].value;
      NEXT;
    }

値のコピーしかしていないので、この場合はmrb->arena_idx=aiは無くてもいい(と思う)。

vm.cをGCC -O3 -Sでコンパイルしてアセンブラを出力してみる。OP_MOVEはoptableの2番目なのでそれを頼りにラベルを検索して、出てきたコードはJMPとかで飛びまくってるからそれらを繋げたものが以下。

L24:
	movl	%ebx, %eax
	shrl	$23, %eax
	sall	$4, %eax
	addl	-768(%ebp), %eax
	shrl	$10, %ebx
	andl	$8176, %ebx
	movl	-768(%ebp), %edx
	addl	%ebx, %edx
	movb	8(%edx), %cl
	movb	%cl, 8(%eax)
	movl	4(%edx), %ecx
	movl	(%edx), %edx
	movl	%edx, (%eax)
	movl	%ecx, 4(%eax)
	movl	-780(%ebp), %ebx
	movl	12(%ebp), %ecx
	movl	%ebx, 4236(%ecx)
	addl	$4, -764(%ebp)
	movl	-764(%ebp), %esi
	movl	(%esi), %ebx
	movl	%ebx, %eax
	andl	$127, %eax
	movl	_optable.3842(,%eax,4), %eax
	jmp	*%eax

mrb->arena_idx=aiをしているのは真ん中ちょい下のmovl -780(%ebp), %ebxから3命令である。OP_MOVEは24命令なので、無駄な処理が1/8も存在することになる。
Cのコードを修正する。

    CASE(OP_MOVE) {
      /* A B    R(A) := R(B) */
      int a = GETARG_A(i);
      int b = GETARG_B(i);

      regs[a].tt = regs[b].tt;
      regs[a].value = regs[b].value;
//      NEXT;
     i=*++pc; goto *optable[GET_OPCODE(i)];
    }
L24:
	movl	%ebx, %eax
	shrl	$23, %eax
	sall	$4, %eax
	addl	-768(%ebp), %eax
	shrl	$10, %ebx
	andl	$8176, %ebx
	movl	-768(%ebp), %edx
	addl	%ebx, %edx
	movb	8(%edx), %cl
	movb	%cl, 8(%eax)
	movl	4(%edx), %ecx
	movl	(%edx), %edx
	movl	%edx, (%eax)
	movl	%ecx, 4(%eax)
	addl	$4, -764(%ebp)
	movl	-764(%ebp), %ecx
	movl	(%ecx), %ebx
	movl	%ebx, %eax
	andl	$127, %eax
	movl	_optable.3842(,%eax,4), %eax
	jmp	*%eax

3命令が消えた。それにともないレジスタesiが使われなくなった。この例では単純に使わないが、もっと入り組んだ命令ではこの効率アップの効果は大きくなるだろう。

ベンチ用コード

from = Time.now
10000000.times do
  a = 1
  b = 2
  c = 3
  a = b
  b = c
  c = a
 end
to = Time.now
p to - from

OP_MOVEを並べるためだけに無駄な処理をしてみた。
んで、mruby --verboseして出てきた実行コード(timesのブロック内)

irep 149 nregs=6 nlocals=5 pools=0 syms=0
000 OP_LOADI    R1      1
001 OP_LOADI    R2      2
002 OP_LOADI    R3      3
003 OP_MOVE     R5      R2
004 OP_MOVE     R1      R5
005 OP_MOVE     R5      R3
006 OP_MOVE     R2      R5
007 OP_MOVE     R5      R1
008 OP_MOVE     R3      R5
009 OP_RETURN   R5

ローカル変数の転送にR5を経由するという激しく無駄なコードが出力されている。コードジェネレータには改善の余地がありそうだ。
んでベンチ。mruby_testのほうが修正したやつ。

D:\test>mruby test.rb
3.765625

D:\test>mruby_test test.rb
3.3125

1割以上速くなっているように見える。
まあ、mrb->arena_idx=aiがよくわかっていないのでバグるかもしれないのだけれども。