パフォーマンスチューニング仕切り直し

前提

(let loop ([i 0])
  (if (= i 10000000)
      '()
      (loop (+ i 1))))

このコードでは Mosh (530 msec)> Gauche (660 msec)。


ところがこのループに浮動小数の加算を入れると逆転。

(let loop ([i 0])
  (if (= i 10000000)
      '()
      (begin (+ i 1.0)
             (loop (+ i 1)))))

Gauche (1574 msec) > Mosh (2000 msec)。

仮説 Flonum の allocation が Mosh > Gauche かも?

どちらも Boehm GC を使っているので GC_PRINT_STATS 環境変数を有効にして allocd size で grep

ただのループ

Gauche 460120 bytes
Mosh 1019040 bytes

ループ回数2倍

Gauche 460120
Mosh 1019040

何もしない

Gauche 460120
Mosh 1019040

ここまではただのループでは allocation が発生しない事が分かる。

浮動小数加算入れる

Gauche 878784
Mosh 1506656


400KB 〜 500KB と同じ程度の allocation が発生している。
あれ。計算が合わないね。 i + 1.0 で少なくとも 8byte の allocation が発生するはずなのだけど。GC が alloc したバイト数かな。
とりあえず浮動小数加算を入れると allocation は増えるが、増加分が GaucheMosh でそれほど変わらない事が分かった。
つまり「仮説1 Flonum の allocation が Mosh > Gauche かも?」は間違いのようだ。

命令列を比べてみよう

ループの一番実行される部分は Mosh の方が短い。

Gauche
 0 CONSTI-PUSH(0) 
 1 LOCAL-ENV(1) 
 2 LREF0-PUSH               ; i
 3 CONST 10000000
 5 BNUMNE 9                 ; (= i 10000000)
 7 CONSTN 
 8 RET 
 9 LREF0-PUSH               ; i
10 CONST 1.0
12 NUMADD2                  ; (+ i 1.0)
13 LREF0                    ; i
14 NUMADDI(1)               ; (+ i 1)
15 PUSH 
16 LOCAL-ENV-JUMP(1) 2      ; (loop (+ i 1))
Mosh
CONSTANT_PUSH 0 
ENTER 1 
REFER_LOCAL_PUSH_CONSTANT_BRANCH_NOT_NUMBER_EQUAL 0 10000000 5 
CONSTANT () 
LOCAL_JMP 15 
REFER_LOCAL_PUSH_CONSTANT 0 1.000000 
NUMBER_ADD 
REFER_LOCAL_PUSH_CONSTANT 0 1 
NUMBER_ADD_PUSH 
SHIFTJ 1 1 -1 
LOCAL_JMP -21 
1.0 => 1

浮動小数をやめて Fixnum にすると Mosh の場合は 1.0 -> 1 に変わるだけ、Gauche でぇあ NUMADD2 ではなく NUMADDI が使われるようになる。
今までの比較から

という仮説が導かれる。

仮説2 浮動小数のときに Mosh の NUMBER_ADD が NUMADD2 より遅い

まずはざっくりと処理を追っていくのが良さそう。

Gauche

vminsn.c

label_NUMADD2:
CASE(SCM_VM_NUMADD2) 
{
#line 1088 "vminsn.scm"
{ScmObj arg;POP_ARG(arg);
if ((SCM_INTP(arg))&&(SCM_INTP(VAL0)))
{long r=(SCM_INT_VALUE(arg))+(SCM_INT_VALUE(VAL0));
if (SCM_SMALL_INT_FITS(r)){VAL0=(SCM_MAKE_INT(r));NEXT1;} else {VAL0=(Scm_MakeInteger(r));NEXT1;}} else 
{VAL0=(Scm_Add(arg,VAL0));NEXT1;}}}

Scm_Add に行く。

ScmObj Scm_Add(ScmObj arg0, ScmObj arg1)
{
    if (SCM_INTP(arg0)) {
        if (SCM_INTP(arg1)) {
            long r = SCM_INT_VALUE(arg0) + SCM_INT_VALUE(arg1);
            return Scm_MakeInteger(r);
        }
        if (SCM_BIGNUMP(arg1)) {
            if (SCM_EXACT_ZERO_P(arg0)) return arg1;
            return Scm_BignumAddSI(SCM_BIGNUM(arg1), SCM_INT_VALUE(arg0));
        }
        if (SCM_RATNUMP(arg1)) {
            if (SCM_EXACT_ZERO_P(arg0)) return arg1;
            return Scm_RatnumAdd(arg0, arg1);
        }
        if (SCM_FLONUMP(arg1)) { // ここ
            if (SCM_EXACT_ZERO_P(arg0)) return arg1;
            return Scm_MakeFlonum((double)SCM_INT_VALUE(arg0)
                                  + SCM_FLONUM_VALUE(arg1));
        }

Scm_MakeFlonum

ScmObj Scm_MakeFlonum(double d)
{
    ScmFlonum *f = SCM_NEW(ScmFlonum);
    SCM_SET_CLASS(f, SCM_CLASS_REAL);
    f->value = d;
    return SCM_OBJ(f);
}
Mosh
CASE(NUMBER_ADD)
{
    const Object n = pop();
    // short cut for Fixnum. Benmarks tell me this is strongly required.
    if (n.isFixnum() && ac_.isFixnum()) {
        const int32_t val = n.toFixnum() + ac_.toFixnum();
        ac_ = Bignum::makeInteger(val);
    } else if (n.isNumber() && ac_.isNumber()) {
        ac_ = Arithmetic::add(n, ac_);
    } else {
        callWrongTypeOfArgumentViolationAfter(this, "+", "number", L2(n, ac_));
    }
    NEXT1;
}

ああ。予想がついたが先に進もう。

        if (n1.isFixnum()) {\
            if (n2.isFixnum()) {\
                return Bignum::op(n1.toFixnum(), n2.toFixnum());\
            } else if (n2.isRatnum()) {\
                return Ratnum::op(n1.toFixnum(), n2.toRatnum());\
            } else if (n2.isFlonum()) {\
                return Flonum::op(n1.toFixnum(), n2.toFlonum());\

このあたりは Gauche と変わらない。
問題は明らかだ。コードを追ってやっと分かった。 isNumber が毎回走るのが遅い。
コード上は一見きれいに見えるのだけど、isNumber の中では isFixnum() || isFlonum() || などとやっているのでそりゃ遅いわ。


isNumber() を呼び出さないようにしてみたところ Gauche より速くなった。
isNumber() を呼び出さないのはまずいので処理を変えよう。

            } else {
                ac_ = Arithmetic::add(n, ac_);
                if (ac_.isFalse()) {
                    callWrongTypeOfArgumentViolationAfter(this, "+", "number", L2(n, ac_));
                }
            }

これでもぎりぎり Mosh > Gauche なのでチューニング完了。ADD に限らず同じような事をやっている部分を修正。
解決。