2012-09-06 8 views
8

Un amico e io stavamo testando utilizzando espressioni compilate per la creazione dell'oggetto invece di Activator.CreateInstance<T> e abbiamo ottenuto risultati interessanti. Abbiamo scoperto che quando eseguivamo lo stesso codice su ciascuna delle nostre macchine, abbiamo visto risultati completamente opposti. Ha ottenuto il risultato atteso, prestazioni significativamente migliori rispetto all'espressione compilata, mentre sono rimasto sorpreso nel vedere ileseguito da 2x.Activator.CreateInstance <T> vs Compiled Expression. Prestazioni inverse su due macchine diverse

Entrambi i computer correvano compilate in .NET 4,0

Computer 1 ha .NET 4.5 installato. Il computer 2 no.

Computer 1 oltre 100000 oggetti:

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

2 del calcolatore oltre 100000 oggetti:

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

Ed ecco il codice:

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

A proposito, [qui] (http://stackoverflow.com/a/969327/217219) è un modo migliore di usare 'Cronometro'. – kprobst

+0

Non ho mai pensato di farlo, o –

+0

Non una risposta, ma non hai bisogno di una cache di dizionari per tutto questo. C# statico lo fa per te. Vedi http://stackoverflow.com/a/16162475/661933 – nawfal

risposta

8

Ci sono almeno due cause per questo:

  • L'overhead di chiamare Type<Test>.New() o System.Activator.CreateInstance<Test>() per la prima volta è relativamente grande. Per questo motivo, ho cambiato da 100000 a 10000000.
  • Crea l'applicazione in modalità di rilascio, eseguila senza debugger.

Con questi due cambiamenti, i due metodi impiegano all'incirca lo stesso tempo. Sul mio sistema, ottengo tra 1100 e 1200 per entrambi i metodi, a volte uno è un po 'più alto, a volte l'altro è.

Nota che Activator.CreateInstance<T>() può chiamare solo il costruttore predefinito, mentre il tuo New() accetta un numero di argomenti. Se rendi il tuo New() meno potente e utilizzi sempre anche il costruttore predefinito, è leggermente più veloce di Activator.CreateInstance<T>() sul mio sistema.

Si noti inoltre che la gestione dei costruttori con parametri non funziona in realtà se si devono utilizzare due diversi costruttori dello stesso tipo, a seconda degli argomenti passati. Scegli una volta quale costruttore usare per il resto dell'intero programma.

Problemi correlati