2012-09-06 7 views
8

Znajomy i ja testowaliśmy używając kompilowanych wyrażeń do tworzenia obiektów zamiast Activator.CreateInstance<T> i uzyskaliśmy interesujące wyniki. Okazało się, że gdy uruchomiliśmy ten sam kod na każdym z naszych maszyn, zobaczyliśmy zupełnie przeciwne wyniki. Otrzymał oczekiwany wynik, znacznie lepszą wydajność ze skompilowanego wyrażenia, podczas gdy ja byłem zaskoczony, widząc, że Activator.CreateInstance<T> wykonuje się 2x.Activator.CreateInstance <T> vs Compiled Expression. Odwrotna wydajność na dwóch różnych maszynach

Oba komputery prowadził kompilowany w .NET 4,0

Komputer 1 ma .NET 4.5 zainstalowany. Komputer 2 nie.

komputerowy 1 ponad 100.000 obiektów:

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

komputerowe 2 ponad 100.000 obiektów:

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

A oto kod:

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

Nawiasem mówiąc, [tutaj] (http://stackoverflow.com/a/969327/217219) jest lepszym sposobem użycia 'Stopwatch'. – kprobst

+0

Nigdy o tym nie myślałem, o –

+0

Nie jest to odpowiedź, ale nie potrzebujesz do tego pamięci podręcznej słownika. C# static robi to za Ciebie. Zobacz http://stackoverflow.com/a/16162475/661933 – nawfal

Odpowiedz

8

Istnieją co przynajmniej dwie przyczyny do tego:

  • szczytowy wywoływania Type<Test>.New() lub System.Activator.CreateInstance<Test>() po raz pierwszy jest stosunkowo duża. Z tego powodu zmieniłem 100000 na 10000000.
  • Zbuduj swoją aplikację w trybie zwolnienia, uruchom bez debuggera.

Obie te metody zajmują mniej więcej tyle samo czasu. W moim systemie otrzymuję od 1100 do 1200 dla obu metod, czasami jeden jest trochę wyższy, czasem drugi.

Należy pamiętać, że Activator.CreateInstance<T>() może wywołać tylko domyślny konstruktor, a Twój New() przyjmuje kilka argumentów. Jeśli sprawisz, że Twój New() będzie mniej wydajny i zawsze użyjesz tam domyślnego konstruktora, jest on nieco szybszy niż Activator.CreateInstance<T>() w moim systemie.

Należy również zauważyć, że obsługa konstruktorów z parametrami faktycznie nie działa, jeśli dwa różne konstruktory tego samego typu powinny być używane, w zależności od przekazanych argumentów. Wybierasz, który konstruktor będzie używał do końca całego programu.

Powiązane problemy