web-dev-qa-db-ja.com

Activator.CreateInstance(type)の代わりにオブジェクトをすばやく作成

アプリケーションのパフォーマンスを改善しようとしています。いくつかの悲しみを引き起こしている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"));

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

34

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

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;
}

値型も効率的な方法で処理します。

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

45
nawfal

これは役立つかもしれません: 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();
16
CD..

問題は、結果をどこかに保存してその結果を何度も使用するのではなく、直接直接何度も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();
}
7
jbtule

同じコードを生成すると、おそらくオーバーヘッドが発生します。

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

ある種のマップまたはすでに使用したDictionaryタイプのタイプを作成し、そのタイプ用に作成されたファクトリメソッドを保持します。

3
Yochai Timmer

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

/// <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つのデリゲートを使用して、特定のタイプの複数のインスタンスを作成します。

0
Kelqualyn