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

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


person Pacerier    schedule 20.05.2011    source источник
comment
Единственный ответ будет зависеть от обстоятельств. Да, он использует отражение, и да, используйте его, если вам это нужно!   -  person Vladislav Zorov    schedule 20.05.2011
comment
Было бы полезно узнать, что новый T () в шаблонах также использует Activator.CreateInstance ‹T› () за кулисами.   -  person Christian.K    schedule 20.05.2011
comment
@Lasse V. Karlsen, @Pacerier: извините, слишком много C ++ в последнее время я вижу при использовании Generics (см. Примечания)   -  person Christian.K    schedule 20.05.2011
comment
@Christian, вы имеете в виду, что когда мы указываем {new} как ограничение типа в дженериках и вызываем new T, оно фактически использует System.Activator?   -  person Pacerier    schedule 20.05.2011
comment
@Pacerier: Да, это то, что он говорит, и это поддерживается страницей MSDN, на которую он ссылается (примечания в его комментарии.)   -  person Lasse V. Karlsen    schedule 20.05.2011
comment
По теме: stackoverflow.com/questions/6708459/   -  person nawfal    schedule 01.07.2016


Ответы (5)


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

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

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

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

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

Test1 - Activator.CreateInstance<T>() 
12342 

Test2 - new T() 
1119 

Test3 - Delegate 
1530 

Baseline 
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
{
}
person Lasse V. Karlsen    schedule 20.05.2011
comment
Интересно отметить, что T Create ‹T› (), где T: new () {return new T ()} работает так же медленно, как Activator.CreateInstance, поэтому при создании экземпляров классов универсальных типов возникает огромный штраф :( - person Arunas; 20.05.2011
comment
Что ж, я бы не сказал, что огромные, так как это по сравнению с другими затратами. Это дороже, чем простой new (), но я сомневаюсь, что это проблема в любых, кроме самых крайних случаях. - person Lasse V. Karlsen; 20.05.2011
comment
@Arunas: Я включил в свой ответ несколько универсальных тестов, но обнаружил, что генерики, использующие Activator.CreateInstance с привязкой, позволяющей вызывать внутренний конструктор, более чем в 6 раз медленнее! Ты хоть представляешь, почему это так? - person JDiMatteo; 19.12.2014

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

  • Activator.CreateInstance
  • новый T ()
  • вызов делегата, который вызывает new 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

Это заимствовано из ответа Лассе В. Карлсена, но, что важно, включает в себя дженерики. Обратите внимание, что указание привязок замедляет Activator с использованием дженериков более чем в 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);
        }
    }
}
person JDiMatteo    schedule 19.12.2014
comment
Хотя это в основном просто отрыгивание отличного ответа Лассе В. Карлсена, я думал, что C # будет более актуальным для большинства людей, и включение общего сравнения new () было очень важным. - person JDiMatteo; 19.12.2014
comment
Кто-нибудь знает, почему тест привязок в 6 раз медленнее и как его ускорить? - person JDiMatteo; 19.12.2014
comment
Вероятно, вы могли бы создать Expression дерево для создания метода с аргументами. Это очевидно, если тип и количество аргументов фиксированы. - person xanatos; 03.07.2015

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

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

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

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

person Rune Grimstad    schedule 20.05.2011

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

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

и

new MyClass();

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

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

person nawfal    schedule 21.02.2013

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

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

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

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

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

вот подробное сравнение результатов между _6 _, _ 7_ и Type.GetInstance()

новый vs Func vs Activator.CreateInstance ()

person Code_Worm    schedule 01.07.2018