CGLIB と ASM

昨日の「徳澤直子ちゃんを語る会」で,「Aspect を適用するとメソッド呼び出しが 20 倍くらい遅くなる」という話をひがさんから聞きました.まぁ,それくらいは遅くなるだろうなぁと思ったりはするわけですが,っていうかもっと遅くなっても不思議はない気もするわけで.
そんなわけで (どんなわけで?),ちょっと確認してみました.
ものすごく単純なインタフェース

public static interface Hoge {
    void foo();

    void bar();
}

と,その実装クラス

public static class HogeImpl implements Hoge {
    public void foo() {
    }

    public void bar() {
    }
}

そして何もしないインターセプタ

public class VoidInterceptor extends AbstractInterceptor {
    public Object invoke(MethodInvocation arg0) throws Throwable {
        return null;
    }
}

を用意.
HogeImpl の foo にだけアスペクトをかけて 1 億回ばかり実行してみました.
その結果...

 アスペクトなしアスペクトあり (CGLIB)
foo()2,156ms31,917ms
bar()2,406ms4,641ms

ふむ.アスペクトなしの場合に foo()bar() で微妙に違うのが謎ですが,アスペクトを適用したクラスの場合,実際にそのアスペクトが適用されないメソッドでも 2 倍くらい,本当にアスペクトが適用されたメソッドの場合は 15 倍くらい遅くなってますね.


そんなに遅いという気はしないわけですが,以前某巨大掲示板に ASM を使った方が CGLIB より速いという話があった気のせいが,あれ? ぐぐっても見つからないよ?
ともあれ (JW),そんなわけで (どんなわけで?),S2AopProxy を ASM を使うように直してみました (これがやりたかっただけだったりして).その結果...

 アスペクトなしアスペクトあり (CGLIB)アスペクトあり (ASM)
foo()2,156ms31,917ms20,323ms
bar()2,406ms4,641ms2,422ms

ふむ.foo() の呼び出しは CGLIB よりも 3 割ばかり速くなりましたが,劇的というほどの違いはない感じ.bar() の呼び出しが速いのは多少イカサマで,生成したクラスでオーバーライドしていないから.(^^;


実は最初,ASM を使っても全然速くならなかったんですよね.結局,MethodInvocationImplインスタンス生成が遅いことが分かって,どうやらそれはインスタンスフィールドの数に左右されるらしいんですね.そこでそのあたりをチューニング (?) してどうにか上記の数字まで改善できたという次第.ということは,その辺りを見直せば CGLIB でももっと速くできるということかも.
とりあえず,ASM の API だけは簡単でした♪ 低水準なんで面倒この上ないというか,Javaアセンブラのソースを生成しているような雰囲気ですね.