Имеет ли System.Activator.CreateInstance(T) проблемы с производительностью, достаточные для того, чтобы препятствовать нам использовать его случайно?
Есть ли у метода System.Activator.CreateInstance(T)
проблемы с производительностью (поскольку я подозреваю, что он использует отражение) достаточно большой, чтобы препятствовать нам использовать его случайно?
Ответы
Ответ 1
Как всегда, единственный правильный ответ на вопрос о производительности - это действительно измерить код.
Здесь приведен пример программы LINQPad, которая проверяет:
- Activator.CreateInstance
- новый T()
- вызов делегата, который вызывает новый 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
{
}
Ответ 2
Вот пример программы С#.NET 4.0, которая проверяет:
- Activator.CreateInstance
- новый T()
- вызов делегата, который вызывает новый 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);
}
}
}
Ответ 3
Это зависит от вашего варианта использования. Если вам нужна очень высокая производительность и вы создаете много объектов, то использование Activator.CreateInstance
может быть проблемой.
Но в большинстве случаев он будет достаточно быстрым, и это очень мощный метод создания объектов.
Фактически, большинство контейнеров IoC/локаторов сервисов/независимо от того, что вы их вызываете, используют этот метод для создания объекта типа, который вы запрашиваете.
Если вы обеспокоены тем, что производительность недостаточно хороша, вы должны выполнить профилирование своего приложения и оценить, есть ли у вас узкое место и где оно находится. Я предполагаю, что вызов Activator.CreateInstance
не будет вашей проблемой.
Ответ 4
Да, существует разница в производительности между вызовом
(MyClass)Activator.CreateInstance(typeof(MyClass));
и
new MyClass();
где последнее быстрее. Но определение того, является ли падение скорости достаточно большим, зависит от вашего домена. В 90% случаев это не проблема. Также обратите внимание, что для типов значений Activator.CreateInstance
снова медленнее из-за unboxing.
Но вот улов: для родовых типов они похожи. new T()
внутренне вызывает Activator.CreateInstance<T>()
, который в свою очередь вызывает RuntimeType.CreateInstanceDefaultCtor(...). Поэтому, если у вас есть общий метод для создания нового экземпляра T
, тогда это не имеет значения, хотя наличие ограничения new()
и вызов new T()
гораздо читабельнее. Здесь соответствующая ссылка по этому вопросу от Jon Skeet.