2011-09-28 17 views
8

Miałem wrażenie, że w .NET casting (nie konwertowanie) jest bardzo tanie i szybkie. Jednak wydaje się, że nie ma to miejsca w przypadku tablicy. Próbuję wykonać tutaj bardzo prostą obsadę, weź T1 [] i rzuć jako T2 []. gdzie T1: T2.Dlaczego przesyłanie tablic (wektorów) jest tak wolne?

Istnieją 3 sposoby, aby to zrobić i jestem nazywając je następujące ::

DropCasting: T2[] array2 = array; 
CastClass: (T2[])array; 
IsInst: array as T2[]; 

I stworzył sposoby, aby to zrobić, niestety, C# zdaje się tworzyć pewne dość dziwny kod w zależności od tego, czy jest ogólny lub nie. (Jeśli generyczny DropCasting używa operatora castclass iw obu przypadkach odmawia emisji 'as' operatora, gdy T1: T2.

W każdym razie napisałem kilka metod dynamicznych i przetestowałem je na kilka zaskakujących wyników (string [] => Object []):.?

DropCast : 223ms 
IsInst : 3648ms 
CastClass: 3732ms 

Dropcasting było ~ 18 razy szybciej niż którykolwiek z operatorów odlewanych Dlaczego rzuca tak powolny dla tablic dla normalnych obiektach takich jak String => obiekt, różnica była znacznie mniejsza poważne:

DropCast : 386ms 
IsInst : 611ms 
CastClass: 519ms 

Benchmark code bel OW:

class Program 
{ 
    static readonly String[] strings = Enumerable.Range(0, 10).Select(x => x.ToString()).ToArray(); 

    static Func<string[], object[]> Dropcast = new Func<Func<string[], object[]>>(
     () => 
     { 
      var method = new DynamicMethod("DropCast", typeof(object[]), new[] { typeof(object), typeof(string[]) },true); 
      var ilgen = method.GetILGenerator(); 
      ilgen.Emit(OpCodes.Ldarg_1); 
      ilgen.Emit(OpCodes.Ret); 
      return method.CreateDelegate(typeof(Func<string[], object[]>)) as Func<string[], object[]>; 
     })(); 
    static Func<string[], object[]> CastClass = new Func<Func<string[], object[]>>(
     () => 
     { 
      var method = new DynamicMethod("CastClass", typeof(object[]), new[] { typeof(object), typeof(string[]) },true); 
      var ilgen = method.GetILGenerator(); 
      ilgen.Emit(OpCodes.Ldarg_1); 
      ilgen.Emit(OpCodes.Castclass, typeof(object[])); 
      ilgen.Emit(OpCodes.Ret); 
      return method.CreateDelegate(typeof(Func<string[], object[]>)) as Func<string[], object[]>; 
     })(); 

    static Func<string[], object[]> IsInst = new Func<Func<string[], object[]>>(
     () => 
     { 
      var method = new DynamicMethod("IsInst", typeof(object[]), new[] { typeof(object), typeof(string[]) },true); 
      var ilgen = method.GetILGenerator(); 
      ilgen.Emit(OpCodes.Ldarg_1); 
      ilgen.Emit(OpCodes.Isinst, typeof(object[])); 
      ilgen.Emit(OpCodes.Ret); 
      return method.CreateDelegate(typeof(Func<string[], object[]>)) as Func<string[], object[]>; 
     })(); 

    static Func<string[], object[]>[] Tests = new Func<string[], object[]>[]{ 
     Dropcast, 
     IsInst, 
     CastClass 
    }; 
    static void Main(string[] args) 
    { 
     int maxMethodLength = Tests.Select(x => GetMethodName(x.Method).Length).Max(); 
     RunTests(1, false, maxMethodLength); 
     RunTests(100000000, true, maxMethodLength); 
    } 

    static string GetMethodName(MethodInfo method) 
    { 
     return method.IsGenericMethod ? 
     string.Format(@"{0}<{1}>", method.Name, string.Join<Type>(",", method.GetGenericArguments())) : method.Name; 
    } 

    static void RunTests(int count, bool displayResults, int maxLength) 
    { 
     foreach (var action in Tests) 
     { 
      Stopwatch sw = Stopwatch.StartNew(); 
      for (int i = 0; i < count; i++) 
      { 
       action(strings); 
      } 
      sw.Stop(); 
      if (displayResults) 
      { 
       Console.WriteLine("{0}: {1}ms", GetMethodName(action.Method).PadRight(maxLength), 
       ((int)sw.ElapsedMilliseconds).ToString().PadLeft(6)); 
      } 
      GC.Collect(); 
      GC.WaitForPendingFinalizers(); 
      GC.Collect(); 
     } 
    } 
} 

Edit zanim ktokolwiek pyta samo prawdziwe rzeczy jak int [] -> uint [], który specyfikacje CLR powinny być oddane bez konwersji.

+0

Chodziło o to, aby masować IL w prawo.W niezwykle trywialnej metodzie, jak '() => ciągi znaków jako obiekt [];' kompilator usunie metodę 'as'. Tworzenie metody dynamicznej uruchamiane jest tylko raz w '.cctor' programu. Po tym każda metoda jest po prostu blobem IL. Dodałem też "instancję" do każdej metody dynamicznej (parametr obiektu), aby uniknąć tasowania podczas używania delegata w metodzie statycznej. –

+0

Tak, przegapiłem drugą warstwę funcs podczas czytania za pierwszym razem. Usunąłem swój komentarz. ;) –

+1

Objęte już kilkakrotnie, tylko można znaleźć stronę główną: http://blogs.msdn.com/b/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two -array-covariance.aspx –

Odpowiedz

0

Ponieważ przesyłasz tablice.

Różnica między trzema fragmentami kodu IL polega na tym, że dwa ostatnie dodają operację IsInst i CastClass. Bardzo niewiele wiadomo na temat typów, więc CLR musi sprawdzić, czy jest to poprawna operacja. To wymaga czasu.

Niewielką różnicę między CastClass i IsInst można wytłumaczyć faktem, że CastClass wykonuje najpierw zerową kontrolę i od razu powiodło się, jeśli argument ma wartość zerową.

Podejrzewam, że spowolnienie jest spowodowane rzutowaniem między tablicami. Konieczne może być wykonanie znacznie więcej pracy, aby upewnić się, że rzutowanie jest prawidłowe. Może być konieczne obejrzenie każdego elementu, aby sprawdzić, czy można go rzucić na docelowy typ elementu. Zgaduję więc, że JIT zamiast wykonywać to wszystko w kodzie maszynowym "inline", wywołuje wywołanie funkcji sprawdzania poprawności.

W rzeczywistości, jeśli przeprowadzasz analizę wydajności, możesz zobaczyć, że tak właśnie się dzieje. Prawie 90% czasu spędza się w funkcji o nazwie "JIT_ChkCastArray".

+0

To ma sens. Wydaje mi się to dziwne, jak gdyby T1: klasa, T2 wtedy obsada musi być zawsze legalna, więc po co zawracać sobie głowę czekiem? –

+0

Domyślam się, że ponieważ tablice do rzutowania są stosunkowo rzadkie, programiści JIT nie zadali sobie trudu, aby je zoptymalizować. Jest to również optymalizacja, którą kompilatory mogą łatwo wykonać, nie wysyłając najpierw instrukcji CastClass lub IsInst. Zasoby JIT są ograniczone, więc każda optymalizacja jest stosunkowo kosztowna i musi być uzasadniona. –

0

To ma dla mnie sens, że odlewanie będzie (prawie) dokładnie tak kosztowne, jak korzystanie z operatora as. W obu scenariuszach należy wykonać test środowiska wykonawczego na typ obiektu i określić, czy jest on zgodny z typem docelowym. Sprawdzenie jest wymagane, aby operacja rzucania mogła rzucić w razie potrzeby InvalidCastException.

Ujmując to w inny sposób, operator as jest operacja obsada - ma to również tę zaletę, że pozwala na odlew bez pokazania wyrzucanie wyjątek (wracając NULL). Można to również zrobić za pomocą kombinacji operatora i obsady, ale to podwoiłoby obciążenie pracą.

+0

Zdaję sobie sprawę, że sprawdzanie typu odbywa się, ale dlaczego jest o wiele droższe? –

+0

Droższy niż co? Twoje własne wyniki pokazują, że casting i operator 'as' są zasadniczo identyczne. (jak twierdzę, powinny być) I twój pierwszy przykład "kontroli" jest zadaniem - praktycznie nie ma opcji. (dla twojego "rzuconego rzutu" kompilator wykonał już całą pracę niezbędną do sprawdzenia poprawności, więc nie ma żadnego trafienia wydajności środowiska wykonawczego) –

+0

Przepraszam, wiem, że 'as' i' (T []) 'są zarówno rzutami, Chciałem zapytać, dlaczego są one tak powolne w porównaniu z przypadkiem kontroli rzucania struny-> obiektu. –

Powiązane problemy