2012-09-06 13 views
8

Un amigo y yo estábamos probando usando expresiones compiladas para la creación de objetos en lugar de Activator.CreateInstance<T> y obtuvimos algunos resultados interesantes. Descubrimos que cuando ejecutamos el mismo código en cada una de nuestras máquinas vimos resultados completamente opuestos. Obtuvo el resultado esperado, un rendimiento significativamente mejor fuera de la expresión compilada, mientras que me sorprendió ver Activator.CreateInstance<T> cabo por 2x.Activator.CreateInstance <T> versus expresión compilada. Rendimiento inverso en dos máquinas diferentes

Ambos equipos corrieron compilados en .NET 4,0

ordenador 1 tiene .NET 4.5 instalado. La computadora 2 no.

ordenador 1 más de 100.000 objetos:

45ms - Type<Test>.New() 
19ms - System.Activator.CreateInstance<Test>(); 

Computer 2 más de 100.000 objetos:

13ms - Type<Test>.New() 
86ms - System.Activator.CreateInstance<Test>(); 

Y aquí está el código:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Linq.Expressions; 

namespace NewNew 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Stopwatch benchmark = Stopwatch.StartNew(); 
      for (int i = 0; i < 100000; i++) 
      { 
       var result = Type<Test>.New(); 
      } 
      benchmark.Stop(); 
      Console.WriteLine(benchmark.ElapsedMilliseconds + " Type<Test>.New()"); 

      benchmark = Stopwatch.StartNew(); 
      for (int i = 0; i < 100000; i++) 
      { 
       System.Activator.CreateInstance<Test>(); 
      } 
      benchmark.Stop(); 
      Console.WriteLine(benchmark.ElapsedMilliseconds + " System.Activator.CreateInstance<Test>();"); 
      Console.Read(); 
     } 


     static T Create<T>(params object[] args) 
     { 
      var types = args.Select(p => p.GetType()).ToArray(); 
      var ctor = typeof(T).GetConstructor(types); 

      var exnew = Expression.New(ctor); 
      var lambda = Expression.Lambda<T>(exnew); 
      var compiled = lambda.Compile(); 
      return compiled; 
     } 
    } 

    public delegate object ObjectActivator(params object[] args); 

    public static class TypeExtensions 
    { 
     public static object New(this Type input, params object[] args) 
     { 
      if (TypeCache.Cache.ContainsKey(input)) 
       return TypeCache.Cache[input](args); 

      var types = args.Select(p => p.GetType()); 
      var constructor = input.GetConstructor(types.ToArray()); 

      var paraminfo = constructor.GetParameters(); 

      var paramex = Expression.Parameter(typeof(object[]), "args"); 

      var argex = new Expression[paraminfo.Length]; 
      for (int i = 0; i < paraminfo.Length; i++) 
      { 
       var index = Expression.Constant(i); 
       var paramType = paraminfo[i].ParameterType; 
       var accessor = Expression.ArrayIndex(paramex, index); 
       var cast = Expression.Convert(accessor, paramType); 
       argex[i] = cast; 
      } 

      var newex = Expression.New(constructor, argex); 
      var lambda = Expression.Lambda(typeof(ObjectActivator), newex, paramex); 
      var result = (ObjectActivator)lambda.Compile(); 
      TypeCache.Cache.Add(input, result); 
      return result(args); 
     } 
    } 

    public class TypeCache 
    { 
     internal static IDictionary<Type, ObjectActivator> Cache; 

     static TypeCache() 
     { 
      Cache = new Dictionary<Type, ObjectActivator>(); 
     } 
    } 

    public class Type<T> 
    { 
     public static T New(params object[] args) 
     { 
      return (T)typeof(T).New(args); 
     } 
    } 

    public class Test 
    { 
     public Test() 
     { 

     } 

     public Test(string name) 
     { 
      Name = name; 
     } 

     public string Name { get; set; } 
    } 
} 
+0

Por cierto, [aquí] (http://stackoverflow.com/a/969327/217219) es una mejor forma de usar 'Cronómetro'. – kprobst

+0

Nunca pensé en hacer eso; o –

+0

No es una respuesta, pero no necesita un caché de diccionario para todo esto. C# static lo hace por ti. Ver http://stackoverflow.com/a/16162475/661933 – nawfal

Respuesta

8

Hay por lo al menos dos causas para esto:

  • La sobrecarga de llamar Type<Test>.New() o System.Activator.CreateInstance<Test>() por primera vez es relativamente grande. Debido a esto, cambié 100000 a 10000000.
  • Cree su aplicación en modo de lanzamiento, ejecútela sin un depurador.

Con estos dos cambios, los dos métodos tardan aproximadamente el mismo tiempo. En mi sistema, obtengo entre 1100 y 1200 para ambos métodos, a veces uno es un poco más alto, a veces el otro es.

Tenga en cuenta que Activator.CreateInstance<T>() solo puede llamar al constructor predeterminado, mientras que su New() acepta una cantidad de argumentos. Si haces tu New() menos potente y siempre usas el constructor predeterminado allí, es un poco más rápido que Activator.CreateInstance<T>() en mi sistema.

Tenga en cuenta también que su manejo de constructores con parámetros en realidad no funciona si se deben usar dos constructores diferentes del mismo tipo, dependiendo de los argumentos pasados. Usted elige una vez qué constructor usar para el resto del programa completo.

Cuestiones relacionadas