DIコンテナにおける自動処理の有無と初期化時間の比較

先日のスマートクライアントのDI戦略(その2)に関して、id:higayasuoさんにコメントを頂いた。

自動バインディングのオーバヘッドは、ほとんどないことが、ベンチマーキングの結果わかっています。
コンポーネントの自動登録についても、DISKのIOが早い場合には、ほとんど一緒。DISKのIOが遅い場合には、自動登録のほうが早くなることも、ベンチマーキングの結果わかっています。

これに関して、自動登録に関してはオーバヘッドは無視できるのには同意。自動バインディングに関しては、Seasar2はオーバヘッドが無いとのことだが、私としては、階層が深く、型情報を検索する範囲が広い、パースするプロパティの数が多い型は、自動バインディングを使った際のオーバヘッドが無視できないというのが見解である。しかし、これは自分が書いたプロトタイプアプリケーションでの感覚的な話であり、もう少し客観的に調べてみる必要があると感じたので、拙作のDIコンテナを使用して一般的なWindowsFormsアプリケーションを作り、コンポーネントの自動登録、依存関係の自動バインディングの有無により、どれだけ処理の差が発生するかを比べてみた。

テストに使用したDIコンテナは、拙作のものであり、Seasar2を参考にしている。しかし、s2dotnetプロジェクトとは違い、正式なSeasar2の.NETポーティング実装ではないし、Seasar2クローンとは言えない実装になっている。例えば、コンテナの生成時に設定ファイルを読み込んで型の定義を生成する部分はXmlReaderを生で使ってハンドラインタフェースは一切使用していなかったり、メタ機能とOGNLによる式評価(s2dotnetJavascript.NETによる式評価)機能は元々実装していなかったり、AOP機能は全面的にCastle.DynamicProxyを利用していたり、Webアプリケーションのための機能は一切持っていない、WindowsFormsのコントロールとの連携機能を持っていたりと、違いがある。従って、一般的なDIコンテナの処理比較ということではなく、あくまで自分のための検証である。

テスト用のアプリケーションはプロジェクト中に、コントロールを全く貼らないForm、つまりSystem.Windows.Forms.Formクラスを単に継承したクラスを3つ用意し、そのうちの一つをメインフォームとして起動するものであり、エントリポイントは以下のようなものである。

namespace AutoBindingTest
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            int start = System.Environment.TickCount;
            IDIContainer di = DIContainerFactory.Create();
            di.Init();
            Debug.WriteLine("DIContainer initializing time = " + (System.Environment.TickCount - start) + "ms");
            Form1 form1 = di.GetComponent("form1") as Form1;
            Application.Run(form1);
        }
    }
}

DIのための設定ファイルは以下のような記述を行った。(語彙はSesar風に書き換え。)

<components>
   <!-- プロジェクト配下のコンポーネント自動登録  -->
    <component name="autoRegister" class="Framework.DI.AutoRegister.EntryAssemblyComponentAutoRegister, Framework" autoBinding="auto">
    </component>
    <!--
    <component name="form1" class="AutoBindingTest.Form1"/>
    <component name="form2" class="AutoBindingTest.Form2"/>
    <component name="form3" class="AutoBindingTest.Form3"/>
    -->
</components>

記述されているコンポーネント"autoRegister"は.NET用であり、プロジェクトのアセンブリ(エントリアセンブリ)に定義された型をパースし、クラスだけを自動登録する機能を持つ。このプロジェクトの場合、3つのフォームクラスの自動登録機能を使わない場合は、"autoRegister"の記述をコメントアウトし、全てのFormの記述のコメントをはずせばよい。
プロジェクトに配置した3つの各フォームには、全て、クラスに対してComponentAttributeカスタム属性を指定してあり、これによりこの型が自動登録された場合のインジェクションポリシが決定される。また、属性中の"AutoBinding"パラメタで、この型に対する自動バインディング処理の有無を簡単に切り替えることができる。(この例の場合、自動バインディングを有効にしている)

namespace AutoBindingTest
{
    [Component(Name = "form1", AutoBinding = AutoBindingType.AUTO)]
    public partial class Form1 : Form
    {
    }
}

ちなみに、計測する時間はDIコンテナを生成(Create)し、初期化処理(Init)を完了するまでの時間をSystem.Environment.TockCountの差で計測したものだけである。また、実行時の誤差を考慮して二度連続して実行した結果を使用する。なお、今回のテストはある処理を行うか否かの時間の差を見るだけなので、ハードウェアのスペックは無視している。

  • 自動登録機能の有無

まずは、自動登録機能の有無だけで、差異があるかを検証してみた。

                 1回目   2回目 
                                                          • -
自動登録機能有: 2109ms, 2140ms 自動登録機能無: 2078ms, 2062ms

両者に有効な差は殆ど無い。予想通り、コンポーネントの自動登録機能のオーバヘッドは無視できると思われる。

次は、自動登録も含めて、自動バインディング機能を切った状態も比較してみた。

                 1回目   2回目 
                                                          • -
自動登録機能有: 1234ms, 1250ms 自動登録機能無: 1062ms, 1047ms

自動登録機能の有無に比べて、自動バインディング機能有から無に変更した時の処理時間が短いことが解る(50%程度)。ちなみに、自動バインディング処理を行う場合、Formクラスで使用可能なプロパティ全てをパースし、バインド可能かどうかを検証する処理が入るが、ログを採ってみると、Formクラスを継承した、Form1,Form2,Form3クラスは全て126個のプロパティがパースの対象になっていた。つまり、自動登録と自動バインディングが有効な場合は、126個×3回のプロパティのパースを行っていることになるため、これが上記の差となっていると思われる。

  • 遅延バインド

以前の日記で書いたことがあるが、スマートクライアントの場合、特に起動時間に神経質になる必要があるため、DIコンテナの初期化時には、必要の無いコンポーネントの生成は避けたいものだ。従って、通常ならば行うDIコンテナの初期化処理を省き、コンポーネントが必要になった時に生成、初期化を行うようにした「遅延バインド」を行えるようした結果も、参考までに掲載しておこう。なお、コンテナの初期化を行わない場合、自動登録用コンポーネント"autoRegister"の初期化メソッドは、そのままでは自動実行されないので、エントリポイントを書き換えて、自動登録有の場合は明示的に"autoRegister"コンポーネントを初期化している。

            IDIContainer di = DIContainerFactory.Create();
            EntryAssemblyComponentAutoRegister register = 
                   di.GetComponent("autoRegister") as EntryAssemblyComponentAutoRegister;
                 1回目   2回目 
                                                          • -
自動登録機能有: 1096ms, 1109ms 自動登録機能無: 906ms, 922ms

この組み合わせで自動登録機能も切った状態では最初に生成されるインスタンスはForm1クラスのインスタンスだけである。当たり前だが、一番初期化に要する時間が短い。

比較結果中で、差異が一番大きい組み合わせを見てみると

自動バインディング有、自動登録機能有、DIコンテナの初期化有 : 2109ms, 2140ms
自動バインディング無、自動登録機能無、DIコンテナの初期化無 :  906ms,  922ms

となる。この差は常駐し続けるサーバアプリケーションでは無視できるため、問題にならないだろう。しかし、クライアントアプリケーションの起動時間の差としては気になるかもしれない。

※これらの比較は全て拙作の.NET専用のDIコンテナと、私の.NET 2.0 Framework環境上での話であり、巷のDIコンテナが全てこのような傾向、比較結果にはなるとは限らないことを、お断りしておきます。