Kowariancja tablic a wydajność

Dziś kolejna ciekawostka z C#. Wiemy, że tablice w c# wspierają kowariancje. Oznacza to, że możemy:

string[]names=new string[5]; object[] objectNames = names; objectNames[0]="hello";

Niestety ma to pewną pułapkę. Co stanie się, gdy będziemy chcieli przypisać niepoprawny typ?

string[] names = new string[5]; object[] objectNames = names; objectNames[0] = 5;

Sytuacja zostanie wykryta przez CLR i wyrzucony zostanie wyjątek ArrayTypeMismatchException. Gdyby nie ten wyjątek, doszłoby do poważnego zamieszania w pamięci, więc jest to absolutnie potrzebne. Co to jednak oznacza? Za każdym razem, gdy zapisujemy dane w tablicy, musi istnieć warunek sprawdzający, czy nie chcemy zapisać niepoprawny typ. Nie ma to znaczenia dla pojedynczych wywołań, ale w pętli (np. while dla wzorca producent\konsument) może to spowodować zauważalne problemy.

Jakie jest rozwiązanie? Najprościej używać value type, które oczywiście nigdy nie spowodują podobnych problemów, stąd CLR nie musi tego za każdym razem sprawdzać. Każdy typ referencyjny może w końcu zostać opakowany w typ prosty. Przykład prostego wrappera:

public struct ReferenceValueWrapper<T> where T:class { public T Value; }

Porównajmy więc wydajność następujących przykładów:

private static void Test1(int arraySize) { Base[] data = new Base[arraySize]; Child child=new Child(); Stopwatch stopwatch = Stopwatch.StartNew(); for (int i = 0; i < arraySize; i++) { data[i] = child; } Console.WriteLine(stopwatch.ElapsedMilliseconds); } private static void Test2(int arraySize) { ReferenceValueWrapper<Base>[] data = new ReferenceValueWrapper<Base>[arraySize]; Child child = new Child(); Stopwatch stopwatch = Stopwatch.StartNew(); for (int i = 0; i < arraySize; i++) { data[i] = new ReferenceValueWrapper<Base>() {Value = child}; } Console.WriteLine(stopwatch.ElapsedMilliseconds); }

Tablica zawiera typ bazowy, a przypisujemy poszczególnym elementom obiekt potomny. Wynik:

image

Różnica czterokrotna… Nie zawsze jednak wydajność jest dużo gorsza. Modyfikując trochę powyższy przykład dostaniemy:

private static void Test1(int arraySize) { Base[] data = new Child[arraySize]; Child child=new Child(); Stopwatch stopwatch = Stopwatch.StartNew(); for (int i = 0; i < arraySize; i++) { data[i] = child; } Console.WriteLine(stopwatch.ElapsedMilliseconds); }

Wynik:

image

Wniosek z tego taki, że w powyższym przykładzie nie został wygenerowany warunek sprawdzający i wyrzucający wyjątek. Mając tablicę z Child, naturalne jest, że nie dojdzie do konfliktu typów.

Leave a Reply

Your email address will not be published.