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

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

Ответы [ 5 ]

49 голосов
/ 20 мая 2011

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

Вот пример 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
{
}
9 голосов
/ 19 декабря 2014

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

  • Activator.CreateInstance
  • new T ()
  • , вызывающий делегат, который вызывает new T ()
  • generic new ()
  • 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);
        }
    }
}
7 голосов
/ 20 мая 2011

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

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

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

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

5 голосов
/ 21 февраля 2013

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

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

и

new MyClass();

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

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

0 голосов
/ 01 июля 2018

Да, на самом деле он имеет проблему с производительностью (по сравнению с 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 ()

...