Есть ли у 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()