30

私たちのアプリケーションのパフォーマンスを向上させようとしています。悲しみを引き起こしているActivator.CreateInstance呼び出しがたくさんあります。

インターフェイス(ITabDocument)に基づいて多数のクラスをインスタンス化し、見回した後、このコードを使用することを考えました。

このコードは、Activator.CreateInstanceコードを使用した場合よりも優れている(実際にはわずかに遅い)

    public static Func<T> CreateInstance<T>(Type objType) where T : class, new()
    {
        var dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + objType.Name, objType, null, objType);
        ILGenerator ilGen = dynMethod.GetILGenerator();
        ilGen.Emit(OpCodes.Newobj, objType.GetConstructor(Type.EmptyTypes));
        ilGen.Emit(OpCodes.Ret);
        return (Func<T>)dynMethod.CreateDelegate(typeof(Func<T>));
    }

私はこれがなぜなのかと思っています。

ITabDocument document = CreateInstance<ITabDocument>(Type.GetType("[Company].Something"));

上記を支援するオブジェクトを作成するより良い方法はありますか?具体的な種類がわからないときは、少し難しいです。


  • あなたはどのくらいの頻度で呼びかけ CreateInstance - その重要な点は、あなたが電話をかけることです。CreateInstance一度だけ覚えている工場の代理人。電話している場合CreateInstance各操作で、はい、遅くなります... - Jon Skeet
  • ファクトリーメソッド、抽象ファクトリー、および同様の創造的パターンなど、いくつかのデザインパターンが役に立ちます。それらはあなたがあなたのオブジェクトを遅くバインドすることを可能にします。あなたはここで見ることができます:oodesign.com - Nickolodeon
  • @Jon Skeet返信していただきありがとうございます。最初はタブ付きドキュメントを多数作成する必要があるため、少なくとも20〜30回呼び出しています。それらはすべて異なるITabbedDocumentの実装です。 - Tiffany Townsend
  • @ティファニー:しかし、彼らはすべて異なる種類ですか?それが重要なことです - あなたはそのメソッドを型ごとに一度だけ呼び出すべきです。 - Jon Skeet
  • おっと、はい、それらはすべて異なる種類です。私の例を編集しました - Tiffany Townsend

5 답변


42

これらの間のベンチマークをいくつか実行しました(最低限の詳細を書き留めます)。

public static T Instance() //~1800 ms
{
    return new T();
}

public static T Instance() //~1800 ms
{
    return new Activator.CreateInstance<T>();
}

public static readonly Func<T> Instance = () => new T(); //~1800 ms

public static readonly Func<T> Instance = () => 
                                 Activator.CreateInstance<T>(); //~1800 ms

//works for types with no default constructor as well
public static readonly Func<T> Instance = () => 
               (T)FormatterServices.GetUninitializedObject(typeof(T)); //~2000 ms


public static readonly Func<T> Instance = 
     Expression.Lambda<Func<T>>(Expression.New(typeof(T))).Compile();  
     //~50 ms for classes and ~100 ms for structs

CDが言っているように、コンパイルされた表現は最速で、そして大きなマージンです。を除くすべての方法(T)FormatterServices.GetUninitializedObject(typeof(T)) デフォルトコンストラクタを持つ型に対してのみ機能します。

ジェネリック型ごとに静的クラスがある場合、コンパイル済みの結果デリゲートをキャッシュするのは簡単です。好きです:

public static class New<T> where T : new()
{
    public static readonly Func<T> Instance = Expression.Lambda<Func<T>>
                                              (
                                               Expression.New(typeof(T))
                                              ).Compile();
}

に注意してくださいnew制約何でも呼んで

MyType me = New<MyType>.Instance();

クラスがメモリに最初にロードされるときを除いて、実行は最速になります。

両方の型を処理するクラスをデフォルトのコンストラクタを使用し、使用しないで持つために、私はハイブリッドアプローチを取りました。ここから

public static class New<T>
{
    public static readonly Func<T> Instance = Creator();

    static Func<T> Creator()
    {
        Type t = typeof(T);
        if (t == typeof(string))
            return Expression.Lambda<Func<T>>(Expression.Constant(string.Empty)).Compile();

        if (t.HasDefaultConstructor())
            return Expression.Lambda<Func<T>>(Expression.New(t)).Compile();

        return () => (T)FormatterServices.GetUninitializedObject(t);
    }
}

public static bool HasDefaultConstructor(this Type t)
{
    return t.IsValueType || t.GetConstructor(Type.EmptyTypes) != null;
}

値型も効率的な方法で扱います。

ご了承ください(T)FormatterServices.GetUninitializedObject(t)失敗しますstring。したがって、空の文字列を返すために、stringに対する特別な処理が行われます。


  • そして、これはどのようにして、その型が実行時にのみ問題として知られているオブジェクトの作成に当てはまるでしょうか?質問のジェネリックスの使用は、返されたオブジェクトを基本クラスにキャストするためだけのものです。指定した例では、返されるインスタンスの型は総称パラメータと同じであるため、通常のコンストラクタを使用できます(初期化されていないオブジェクトも作成できるという事実を除く)。 - Richard Collette
  • @RichardCollette私はその質問に直接答えないことに同意しますが、素晴らしい小さなヘルパークラスを作成する方法を示します(このクラスは不完全なので、1日更新します)。それにもかかわらず、OPのための実行可能な解決策にそれを微調整することはそれほど難しいことではありません(代わりに提供されたタイプを使うだけです)typeof(T)) - nawfal
  • 最初のもの、単純な総称、new T()...それは正しくありません。これは最後にラムダバージョンより遅くすることはできません。総称はコンパイル時に処理されるので、これは非ジェネリックコンストラクタと同じくらい速いはずです。再確認できますか。それはあなたのさもなければ優秀な投稿のすべての結果に疑問を投げかけるので、それは重要です! - Timo
  • @Timoは、「総称はコンパイル時に処理される」という意味によって異なります。確かに、いくつかの静的チェックが行われます。しかしTは常にランタイム型を持ちます。私はコンパイラが一般的なケースでのリフレクションに頼らないように十分に賢くあるべきだと思いますが、残念ながらそれはします。それがポイントです。これを確認するには、ILを調べます。 - nawfal
  • このブログを読むcodeblog.jonskeet.uk/2011/08/22/…Jon Skeetから。また、ユーザーによるさまざまなベンチマークのコメントすべてが私の結果を検証します。そのブログでの私のコメントにも注意してください。これは、今ではさらに遅くなっているRoslynでの動作の変化を強調しています。 - nawfal

15

これは役に立つかもしれません:Activator.CreateInstanceやConstructorInfo.Invokeを使用しないでください。コンパイル済みのラムダ式を使用してください。

// Make a NewExpression that calls the ctor with the args we just created
NewExpression newExp = Expression.New(ctor, argsExp);                  

// Create a lambda with the New expression as body and our param object[] as arg
LambdaExpression lambda = Expression.Lambda(typeof(ObjectActivator), newExp, param);            


// Compile it
ObjectActivator compiled = (ObjectActivator)lambda.Compile();


  • おかげで、私は以前にこれを見ましたが、私が必要とするようにアドホックに呼ぶことができるかどうか確信が持てませんでした。 - Tiffany Townsend
  • リンクをありがとう、それはこのブログにつながりますrogeralsing.com/2008/02/28/linq-expressions-creating-objects私はこれを読んでいて、今ではこれまで見た中で最高のブログの1つになるかもしれないと思います。どうもありがとうございます - RichK
  • これは理論的には質問に答えますが、それは好ましいでしょうここに答えの本質的な部分を含み、参照用のリンクを提供する。回答を編集してこれを修正してから、「モデレータの介入が必要」としてフラグを立ててください。そして削除の取り消しを要求します。 - Matt

7

問題は、結果をどこかに保存してその結果を何度も使用するのではなく、CreateInstanceを直接何度も直接呼び出す場合は、おそらく先に進んでその内部にキャッシュするかどうかです。

internal static class DelegateStore<T> {
     internal static IDictionary<string, Func<T>> Store = new ConcurrentDictionary<string,Func<T>>();
}

public static T CreateInstance<T>(Type objType) where T : class
{
    Func<T> returnFunc;
    if(!DelegateStore<T>.Store.TryGetValue(objType.FullName, out returnFunc)) {
        var dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + objType.Name, objType, null, objType);
        ILGenerator ilGen = dynMethod.GetILGenerator();
        ilGen.Emit(OpCodes.Newobj, objType.GetConstructor(Type.EmptyTypes));
        ilGen.Emit(OpCodes.Ret);
        returnFunc = (Func<T>)dynMethod.CreateDelegate(typeof(Func<T>));
        DelegateStore<T>.Store[objType.FullName] = returnFunc;
    }
    return returnFunc();
}


  • あなたは実際にあなたの中に辞書を必要としませんDelegateStore<T>そのジェネリッククラスは自動的にユニークなパーTすでにインスタンス化されています。それがそれであってもこれは本当ですstaticクラス。だからあなたが必要とするすべてDelegateStore<T>単純ですinternal static Func<T> _cached_func;あなたがそれを持っているので、あなたはたくさんの辞書を作成しています。T - それぞれにキャッシュされたデリゲートを1つだけ含みます。 - Glenn Slayden

3

あなたはおそらく同じコードの生成からいくらかのオーバーヘッドを得ています。

ILGeneratorファクトリ用のコードを動的に作成します。

地図を作成するDictionaryすでに使用した型のうち、ファクトリメソッドをその型用に作成したままにします。


0

コンストラクタを直接呼び出す、デリゲートを構築するための汎用メソッド。 指定されたデリゲート型のシグネチャを使用して、指定された型のコンストラクタを自動的に検索し、その型のデリゲートを構築します。ここにコード:

/// <summary>
/// Reflective object construction helper.
/// All methods are thread safe.
/// </summary>
public static class Constructor
{
    /// <summary>
    /// Searches an instanceType constructor with delegateType-matching signature and constructs delegate of delegateType creating new instance of instanceType.
    /// Instance is casted to delegateTypes's return type. 
    /// Delegate's return type must be assignable from instanceType.
    /// </summary>
    /// <param name="delegateType">Type of delegate, with constructor-corresponding signature to be constructed.</param>
    /// <param name="instanceType">Type of instance to be constructed.</param>
    /// <returns>Delegate of delegateType wich constructs instance of instanceType by calling corresponding instanceType constructor.</returns>
    public static Delegate Compile(Type delegateType,Type instanceType)
    {
        if (!typeof(Delegate).IsAssignableFrom(delegateType))
        {
            throw new ArgumentException(String.Format("{0} is not a Delegate type.",delegateType.FullName),"delegateType");
        }
        var invoke = delegateType.GetMethod("Invoke");
        var parameterTypes = invoke.GetParameters().Select(pi => pi.ParameterType).ToArray();
        var resultType = invoke.ReturnType;
        if(!resultType.IsAssignableFrom(instanceType))
        {
            throw new ArgumentException(String.Format("Delegate's return type ({0}) is not assignable from {1}.",resultType.FullName,instanceType.FullName));
        }
        var ctor = instanceType.GetConstructor(
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, parameterTypes, null);
        if(ctor == null)
        {
            throw new ArgumentException("Can't find constructor with delegate's signature","instanceType");
        }
        var parapeters = parameterTypes.Select(Expression.Parameter).ToArray();

        var newExpression = Expression.Lambda(delegateType,
            Expression.Convert(Expression.New(ctor, parapeters), resultType),
            parapeters);
        var @delegate = newExpression.Compile();
        return @delegate;
    }
    public static TDelegate Compile<TDelegate>(Type instanceType)
    {
        return (TDelegate) (object) Compile(typeof (TDelegate), instanceType);
    }
}

の一部ですYappiプロジェクトの情報源それを使用すると、パラメータ付きのコンストラクタ(refおよびoutパラメータを除く)を含む、任意の型の任意のコンストラクタを呼び出すデリゲートを構築できます。

使用例

var newList = Constructor.Compile<Func<int, IList<String>>>(typeof (List<String>));
var list = newList(100);

デリゲートを構築したら、それを静的辞書かクラスの静的フィールドのいずれかの場所に汎用パラメータで格納します。毎回新しいデリゲートを構築しないでください。与えられた型の複数のインスタンスを構築するために1つのデリゲートを使います。

リンクされた質問


関連する質問

最近の質問