Есть ли у System.Activator.CreateInstance(T) проблемы с производительностью, достаточно большие, чтобы отговорить нас от ее случайного использования?

Есть ли System.Activator.CreateInstance(T) метод имеет проблемы с производительностью (так как я подозреваю, что он использует отражение) достаточно большой, чтобы отговорить нас от его использования случайно?

5 ответов

Решение

Как всегда, единственный правильный способ ответить на вопрос о производительности - это измерить код.

Вот пример программы LINQPad, которая тестирует:

  • Activator.CreateInstance
  • новый T()
  • вызов делегата, который вызывает новый T()

Как всегда, возьмите программу производительности с небольшим количеством соли, здесь могут быть ошибки, которые искажают результаты.

Вывод (временные значения в миллисекундах):

 Test1 - Activator.CreateInstance  () 
12342 

Test2 - новый T() 
1119 

Test3 - делегат 
1530 

базисный 
578 

Обратите внимание, что вышеуказанные сроки относятся к 100.000.000 (100 миллионам) конструкций объекта. Перегрузка может не быть реальной проблемой для вашей программы.

Предостерегающий вывод: Activator.CreateInstance<T> занимает примерно в 11 раз больше времени, чтобы сделать ту же работу, что и new T() делает, и делегат занимает примерно в 1,5 раза больше. Обратите внимание, что конструктор здесь ничего не делает, поэтому я попытался измерить только издержки различных методов.

Изменить: я добавил базовый вызов, который не создает объект, но делает все остальное, и рассчитал это тоже. Исходя из этого, похоже, что делегат занимает на 75% больше времени, чем простой new(), а Activator.CreateInstance - примерно на 1100%.

Однако это микрооптимизация. Если вам действительно нужно это сделать и извлечь последнюю унцию производительности какого-то критичного по времени кода, я бы либо вручную написал делегат для использования, либо, если это невозможно, т.е. вам нужно предоставить тип во время выполнения, я бы использовал Reflection.Emit для динамического создания этого делегата.

В любом случае и вот мой реальный ответ:

Если у вас проблемы с производительностью, сначала определите, где находится ваше узкое место. Да, приведенные выше временные параметры могут указывать на то, что Activator.CreateInstance имеет больше накладных расходов, чем динамически создаваемый делегат, но в вашей кодовой базе может быть гораздо больше рыбы, которую нужно жарить, прежде чем вы достигнете (или даже должны будете добраться) до этого уровня оптимизации.

И просто чтобы убедиться, что я действительно отвечу на ваш конкретный вопрос: нет, я бы не стал препятствовать использованию Activator.CreateInstance. Вы должны знать, что он использует рефлексию, так что вы знаете, что если это превысит ваши списки узких мест профилирования, то вы сможете что-то с этим сделать, но тот факт, что он использует рефлексию, не означает, что это узкое место.

Программа:

void Main()
{
    const int IterationCount = 100000000;

    // warmup
    Test1();
    Test2();
    Test3();
    Test4();

    // profile Activator.CreateInstance<T>()
    Stopwatch sw = Stopwatch.StartNew();
    for (int index = 0; index < IterationCount; index++)
        Test1();
    sw.Stop();
    sw.ElapsedMilliseconds.Dump("Test1 - Activator.CreateInstance<T>()");

    // profile new T()
    sw.Restart();
    for (int index = 0; index < IterationCount; index++)
        Test2();
    sw.Stop();
    sw.ElapsedMilliseconds.Dump("Test2 - new T()");

    // profile Delegate
    sw.Restart();
    for (int index = 0; index < IterationCount; index++)
        Test3();
    sw.Stop();
    sw.ElapsedMilliseconds.Dump("Test3 - Delegate");

    // profile Baseline
    sw.Restart();
    for (int index = 0; index < IterationCount; index++)
        Test4();
    sw.Stop();
    sw.ElapsedMilliseconds.Dump("Baseline");
}

public void Test1()
{
    var obj = Activator.CreateInstance<TestClass>();
    GC.KeepAlive(obj);
}

public void Test2()
{
    var obj = new TestClass();
    GC.KeepAlive(obj);
}

static Func<TestClass> Create = delegate
{
    return new TestClass();
};

public void Test3()
{
    var obj = Create();
    GC.KeepAlive(obj);
}

TestClass x = new TestClass();
public void Test4()
{
    GC.KeepAlive(x);
}

public class TestClass
{
}

Вот пример программы на C# .NET 4.0, которая тестирует:

  • Activator.CreateInstance
  • новый T()
  • вызов делегата, который вызывает новый T()
  • универсальный новый ()
  • Activator.CreateInstance с использованием универсального
  • Activator.CreateInstance, используя общие и нестандартные привязки (например, для вызова внутреннего конструктора)

Вывод (временные значения в миллисекундах от мощной машины 2014 года с версией сборки x86):

Test1 - Activator.CreateInstance<T>(): 8542
Test2 - new T() 1082
Test3 - Delegate 1214
Test4 - Generic new() 8759
Test5 - Generic activator 9166
Test6 - Generic activator with bindings 60772
Baseline 322

Это взято из ответа Лассе В. Карлсена, но, что важно, включает дженерики. Обратите внимание, что указание привязок замедляет Активатор с использованием обобщений более чем в 6 раз!

using System;
using System.Reflection;
using System.Diagnostics;

namespace ConsoleApplication1
{
    public class TestClass
    {
    }

    class Program
    {
        static void Main(string[] args)
        {
            const int IterationCount = 100000000;

            // warmup
            Test1();
            Test2();
            Test3();
            Test4<TestClass>();
            Test5<TestClass>();
            Test6<TestClass>();

            // profile Activator.CreateInstance<T>()
            Stopwatch sw = Stopwatch.StartNew();
            for (int index = 0; index < IterationCount; index++)
                Test1();
            sw.Stop();
            Console.WriteLine("Test1 - Activator.CreateInstance<T>(): {0}", sw.ElapsedMilliseconds);

            // profile new T()
            sw.Restart();
            for (int index = 0; index < IterationCount; index++)
                Test2();
            sw.Stop();
            Console.WriteLine("Test2 - new T() {0}", sw.ElapsedMilliseconds);

            // profile Delegate
            sw.Restart();
            for (int index = 0; index < IterationCount; index++)
                Test3();
            sw.Stop();
            Console.WriteLine("Test3 - Delegate {0}", sw.ElapsedMilliseconds);

            // profile generic new()
            sw.Restart();
            for (int index = 0; index < IterationCount; index++)
                Test4<TestClass>();
            sw.Stop();
            Console.WriteLine("Test4 - Generic new() {0}", sw.ElapsedMilliseconds);

            // generic Activator without bindings
            sw.Restart();
            for (int index = 0; index < IterationCount; index++)
                Test5<TestClass>();
            sw.Stop();
            Console.WriteLine("Test5 - Generic activator {0}", sw.ElapsedMilliseconds);

            // profile Activator with bindings
            sw.Restart();
            for (int index = 0; index < IterationCount; index++)
                Test6<TestClass>();
            sw.Stop();
            Console.WriteLine("Test6 - Generic activator with bindings {0}", sw.ElapsedMilliseconds);


            // profile Baseline
            sw.Restart();
            for (int index = 0; index < IterationCount; index++)
                TestBaseline();
            sw.Stop();
            Console.WriteLine("Baseline {0}", sw.ElapsedMilliseconds);
        }

        public static void Test1()
        {
            var obj = Activator.CreateInstance<TestClass>();
            GC.KeepAlive(obj);
        }

        public static void Test2()
        {
            var obj = new TestClass();
            GC.KeepAlive(obj);
        }

        static Func<TestClass> Create = delegate
        {
            return new TestClass();
        };

        public static void Test3()
        {
            var obj = Create();
            GC.KeepAlive(obj);
        }

        public static void Test4<T>() where T : new()
        {
            var obj = new T();
            GC.KeepAlive(obj);
        }

        public static void Test5<T>()
        {
            var obj = ((T)Activator.CreateInstance(typeof(T)));
            GC.KeepAlive(obj);
        }

        private const BindingFlags anyAccess = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

        public static void Test6<T>()
        {
            var obj = ((T)Activator.CreateInstance(typeof(T), anyAccess, null, null, null));
            GC.KeepAlive(obj);
        }

        static TestClass x = new TestClass();
        public static void TestBaseline()
        {
            GC.KeepAlive(x);
        }
    }
}

Это зависит от вашего варианта использования. Если вам нужна очень высокая производительность и вы создаете много объектов, используйте Activator.CreateInstance может быть проблемой.

Но в большинстве случаев это будет достаточно быстро, и это очень мощный метод создания объектов.

Фактически, большинство IoC-контейнеров / локаторов служб / как бы вы их ни называли, используют этот метод для создания объекта запрашиваемого вами типа.

Если вы обеспокоены тем, что производительность недостаточно высока, вам следует выполнить профилирование вашего приложения и определить, есть ли у вас узкое место и где оно находится. Я думаю, что призыв к Activator.CreateInstance не будет твоей проблемой.

Да есть разница в производительности между звонками

(MyClass)Activator.CreateInstance(typeof(MyClass));

а также

new MyClass();

где последний быстрее. Но определить, достаточно ли падение скорости, зависит от вашего домена. В 90% случаев это не проблема. Также обратите внимание, что для типов значений Activator.CreateInstance снова медленнее из-за распаковки.

Но здесь есть одна загвоздка: для универсальных типов они похожи. new T() внутренние звонки Activator.CreateInstance<T>() который в свою очередь вызывает RuntimeType.CreateInstanceDefaultCtor (...). Так что если у вас есть универсальный метод для создания нового экземпляра Tто это не должно иметь значения, хотя new() ограничение и вызов new T() гораздо более читабельным. Вот соответствующая ссылка на эту тему от Джона Скита.

Да, на самом деле это проблема производительности (по сравнению с new()) так как он использует Reflection и статический компилятор проверяет специально, когда вы передаете ему параметры (отправляя параметры конструктору класса) вместо использования конструктора по умолчанию (как показано ниже)

//Too bad!!!
T someResult = (T)Activator.CreateInstance(
 typeof(T),   
 //parameter
 new object[] {id}
);

Использовать его или нет, на мой взгляд, зависит от двух вещей:

Сначала тип вашего приложения и, конечно, его масштаб (и это типичный трафик)

И второе (и что более важно), как и где вы используете Activator.CreateInstance метод, например, если вы используете его в каждом запросе с одним или несколькими параметрами конструктора (как я уже упоминал, использование с параметрами конструктора почти на одну десятую медленнее, чем при использовании без параметров (конструктор по умолчанию)), производительность вашего приложения снижается почти в значительной степени, но для другого экземпляра, если вы используете его один раз (например, в application_start) и без параметра конструктора, он почти действует как new ключевое слово

Вот подробное сравнение производительности new(),Activator.CreateInstance а также Type.GetInstance()

новый против Func против Activator.CreateInstance()

Другие вопросы по тегам