.net - ¿System.Activator.CreateInstance(T) tiene problemas de rendimiento lo suficientemente grandes como para desalentarnos a usarla de forma casual?
performance instantiation (5)
¿El método System.Activator.CreateInstance(T)
tiene problemas de rendimiento (ya que sospecho que usa reflexión) lo suficientemente grande como para desalentarnos de usarlo de manera casual?
Aquí hay una muestra del programa C # .NET 4.0 que prueba:
- Activator.CreateInstance
- nueva T ()
- llamando a un delegado que llama a nueva T ()
- genérico nuevo ()
- Activator.CreateInstance usando un genérico
- Activator.CreateInstance usando enlaces genéricos y no predeterminados (por ejemplo, para llamar al constructor interno)
La salida (los valores de tiempo se expresan en milisegundos a partir de una máquina robusta de 2014 con compilación de versión 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
Esto se adopta de la respuesta de Lasse V. Karlsen, pero incluye los genéricos. ¡Tenga en cuenta que la especificación de enlaces ralentiza el Activador usando genéricos en más de un factor de 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);
}
}
}
Como siempre, la única forma correcta de responder una pregunta sobre el rendimiento es medir el código.
Aquí hay un programa LINQPad de muestra que prueba:
- Activator.CreateInstance
- nueva T ()
- llamando a un delegado que llama a T nuevo)
Como siempre, tome el programa de rendimiento con un grano de sal, puede haber errores aquí que sesguen los resultados.
La salida (los valores de tiempo están en milisegundos):
Test1 - Activator.CreateInstance<T>() 12342 Test2 - new T() 1119 Test3 - Delegate 1530 Baseline 578
Tenga en cuenta que los tiempos anteriores son para 100.000.000 (100 millones) de construcciones del objeto. La sobrecarga puede no ser un problema real para su programa.
La conclusión cautelar sería que Activator.CreateInstance<T>
está tomando aproximadamente 11 veces más tiempo para hacer el mismo trabajo que un new T()
y un delegado tarda aproximadamente 1.5 veces más. Tenga en cuenta que el constructor aquí no hace nada, por lo que solo intenté medir la sobrecarga de los diferentes métodos.
Edición: agregué una llamada de línea de base que no construye el objeto, pero hace el resto de las cosas, y también lo cronometré. Con eso como una línea de base, parece que un delegado toma un 75% más de tiempo que un simple nuevo (), y el Activator.CreateInstance toma alrededor de un 1100% más.
Sin embargo, esto es micro-optimización. Si realmente necesita hacer esto, y obtener la última onza de rendimiento de algún código crítico en el tiempo, codificaría a mano un delegado para que lo use, o si eso no es posible, es decir. necesita proporcionar el tipo en tiempo de ejecución, usaría Reflection.Emit para producir ese delegado dinámicamente.
En cualquier caso, y aquí está mi verdadera respuesta:
Si tiene un problema de rendimiento, primero mida dónde está su cuello de botella. Sí, los tiempos indicados anteriormente pueden indicar que Activator.CreateInstance tiene más sobrecarga que un delegado creado dinámicamente, pero puede haber peces mucho más grandes para freír en su base de código antes de llegar (o incluso tener que llegar) a este nivel de optimización.
Y solo para asegurarme de que realmente respondo a su pregunta concreta: No, no desalentaría el uso de Activator.CreateInstance. Debe tener en cuenta que utiliza la reflexión para que sepa que si esto encabeza su lista de perfiles de cuellos de botella, entonces es posible que pueda hacer algo al respecto, pero el hecho de que utiliza la reflexión no significa que sea el cuello de botella.
El programa:
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
{
}
Depende de tu caso de uso. Si necesita un rendimiento muy alto y está creando muchos objetos, el uso de Activator.CreateInstance
puede ser un problema.
Pero en la mayoría de los casos, será lo suficientemente rápido y es un método muy poderoso para crear objetos.
De hecho, la mayoría de los IoC Containers / Service locators / como se llame, usan este método para crear un objeto del tipo que está solicitando.
Si le preocupa que el rendimiento no sea lo suficientemente bueno, debe hacer un perfil de su aplicación y medir si tiene un cuello de botella y dónde está. Supongo que la llamada a Activator.CreateInstance
no será tu problema.
Sí, en realidad tiene un problema de rendimiento (en comparación con new()
) ya que utiliza las comprobaciones de compilación estática y de Reflection
, especialmente cuando le pasas parámetros (enviando parámetros al constructor de la clase) en lugar de usar el constructor predeterminado (como a continuación)
//Too bad!!!
T someResult = (T)Activator.CreateInstance(
typeof(T),
//parameter
new object[] {id}
);
Para usarlo o no, en mi opinión, depende de dos cosas:
Primero, su tipo de aplicación y, por supuesto, su escala (y es tráfico típico)
Y segundo (y más importante) cómo y dónde usas el método Activator.CreateInstance
, por ejemplo, si lo usas en cada solicitud con uno o más parámetros de constructor (como mencioné usando con parámetros de constructor es casi un décimo más lento que sin parámetros ( constructor predeterminado)), el rendimiento de su aplicación se deteriora casi en una cantidad significativa, pero para otra instancia, si lo usa una vez (en application_start por ejemplo) y sin parámetro de constructor , casi actúa como una new
palabra clave
Aquí hay una comparación de referencia detallada entre new()
, Activator.CreateInstance
y Type.GetInstance()
Sí, hay una diferencia de rendimiento entre las llamadas
(MyClass)Activator.CreateInstance(typeof(MyClass));
y
new MyClass();
donde este último es más rápido. Pero determinar si la caída de velocidad es lo suficientemente grande depende de su dominio. En el 90% del caso, no es un problema. También tenga en cuenta que para los tipos de valores, Activator.CreateInstance
es de nuevo más lento debido al unboxing involucrado.
Pero aquí está el truco : para los tipos genéricos, son similares. new T()
llama internamente a Activator.CreateInstance<T>()
que a su vez llama a RuntimeType.CreateInstanceDefaultCtor(...) . Entonces, si tiene un método genérico para crear una nueva instancia de T
, entonces no debería importar, aunque tener una new()
restricción new()
y llamar a una new T()
es mucho más legible. Aquí hay un enlace relevante sobre el tema de Jon Skeet.