DIにおけるリフレクション情報のキャッシュ (3

.NETのDIコンテナにおいては、リフレクション情報のキャッシュが有効か、無効なのかに関して、そろそろ決着をつけようと思う。前回の日記ではキャッシュの有無によりアプリケーション起動まで、つまりDIコンテナの生成〜初期化の時間に有意な差は発生しなかった。

                                     1回目   2回目
                                                                                                    • -
リフレクション情報キャッシュ無し : 1328ms, 1344ms リフレクション情報キャッシュ有り : 1359ms, 1297ms

ならば、キャッシュの効果が出やすい状況、例えば大量のコンポーネントを生成し、連続して取得する場合ではどうなのかを試してみよう。簡単な例で、尚且つインジェクションを含めるため、以下のようなインタフェースとクラスを用意した。

public interface ISayHello
{
    string Name { get; set; }
    void Hello();
}
class Class1 : ISayHello
{
    private string name;

    public Class1()
        : base()
    {}
    [Binding(BindingType = BindingType.MUST, Value = "クラス1")]
    public string Name
    {
        get { return name; }
        set { name = value; }
    }
    public void Hello()
    {
        System.Console.WriteLine("Hello " + name);
    }
}
class Class2 : ISayHello
{
    private string name;

    public Class2()
        : base()
    {}
    [Binding(BindingType = BindingType.MUST, Value = "クラス2")]
    public string Name
    {
        get { return name; }
        set { name = value; }
    }
    public void Hello()
    {
        System.Console.WriteLine("Hello " + name);
    }
}

そして、これら二つのクラスのインスタンスを2000個生成する設定ファイルを用意した。



    
    
    :
    : 以下略
    

キャッシュの有無に関しては、前回の日記で実装したTypeDescImplクラスを「強くキャッシュする」という意味でStrongCacheTypeDescクラス、一切キャッシュを実装せず、ITypeDescインタフェースのメソッドは全てTypeクラスに委譲するNullChacheTypeDescクラスと命名し、それらをTypeDescFactoryで使い分けてテストを行うことにした。
以下、変更したTypeDescFactoryである。(Type毎にDictionaryに格納する操作は最低限の効率化だろうということで、今回のテストでは変更していない。仮に毎回インスタンスを生成するようにしたとしても明解な性能差は出ないだろう)

public static ITypeDesc GetTypeDesc(Type type)
{
    if (type == null) throw new IllegalMethodRuntimeException("type parameter must be not null");
    if (typeDescMap.ContainsKey(type))
    {
        return typeDescMap[type];
    }
    else
    {
        lock (typeDescMap)
        {
            if (typeDescMap.ContainsKey(type))
            {
                return typeDescMap[type];
            }
            else
            {
                ITypeDesc desc = CreateTypeDesc(type);
                typeDescMap.Add(type, desc);
                return desc;
            }
        }
    }
}
private static ITypeDesc CreateTypeDesc(Type type)
{
#if TYPEDESC_STRONGCACHE
    return new StrongCacheTypeDesc(type);
#else
    return new NullCacheTypeDesc(type);
#endif
}

以上の準備を終えたあと、プリプロセッサ条件"TYPEDESC_STRONGCACHE"の有無でビルドし、2度ずつ以下のコンソールアプリケーションを実行することで時間を計測した。

static void Main(string[] args)
{
    int start = System.Environment.TickCount;
    IDIContainer container = DIContainerFactory.Create();//遅延生成のためInitは実行しない
    int i = 0;
    ISayHello component = container.GetComponent("instance" + i); 
    while ( component != null)
    {
        component.Hello();
        i++;
        component = container.GetComponent("instance" + i);
    }
    System.Console.WriteLine((System.Environment.TickCount - start) + "ms");
    System.Environment.Exit(0);
}

以下が、その結果である。

                          1回目   2回目
                                                                          • -
NullCacheTypeDesc : 2437ms, 2468ms StrongCacheTypeDesc : 2391ms, 2375ms

キャッシュを有効にしたほうが若干は速いが、ここでも差は微小(100ms以内)でありキャッシュの効果があるとは言い難い。Aspectを設定したり、自動バインド処理を増やしたりすれば、もう少し差は出るのだろうが、これ位の処理では、やはり.NETに関してはリフレクション情報をキャッシュする意味は無いというのが結論だろう。

テストは以下のハードウェアとソフトウェアを用いて行ったが、計測に至っては厳密なものではなく、私の開発環境で簡易な管理の下で実行した参考値であり、この実行結果が使用したハードウェアとソフトウェアの性能を証明するものではないことをお断りしておく。

CPU   : Intel Xeon Processor 2.0Ghz × 2 (HyperThreading Enabled)
Memory: 1GB (ECC On)
O/S   : WindowsXP Professional(SP2)
CLR   : .NET Framework CLR 2.0.50727

追記:

.NETにおいても型情報のキャッシュが効果有りと思われる部分がある。それはDynamicProxyを使用したAOP用のプロキシ型をキャッシュすることだ。DynamicProxyは名前こそプロキシと付いているが、実際のAspectの織り込みは、動的に拡張された型により実現されている。この拡張された型は、型の生成時に元の型のAOPの対象の全てのメソッド(インタフェース、又はVirtual識別子のついたメソッド)をコールバックするコードを生成するために、型自体の生成に非常に時間がかかるのである。従ってこの型の情報をキャッシュすることで二度目以降の処理時間を短縮できる可能性が高い。