wtorek, 24 kwietnia 2012

LINQ vs IComparable czas sortowania / performance

Zrobiłem mały test.
Chciałem sprawdzić co jest wydajniejsze, LINQ czy użycie IComparera.
Odpowiedź: "to zależy?"


Na początek zrobiłem małą klasę obiektu do testowania, która wyglądała tak:
Następnie w głównej pętli programu napisałem następujący kod:

static void Main(string[] args)
{
    string name = "Mr. Tomek";

    Random r = new Random();
    int size = 50;

    Stopwatch stopWatch = new Stopwatch();

    while (size < 1000000)
    {
        int linqTime = 0;
        int sortTime = 0;

        int repeats = 10;
        Console.WriteLine("Size = {0}", size);

        for (int a = 0; a < repeats; ++a)
        {
            Person[] people1 = new Person[size];
            Person[] people2 = new Person[size];

            for (int i = 0; i < size; i++)
            {
                people1[i] = new Person();
                people1[i].Age = r.Next(0, 10000);
                people1[i].Name = name;

                people2[i] = new Person();
                people2[i].Age = people1[i].Age;
                people2[i].Name = people1[i].Name;
            }

            stopWatch.Start();
            Array.Sort(people1);
            stopWatch.Stop();
            sortTime += (int)stopWatch.ElapsedMilliseconds;

            stopWatch.Start();
            var sort = from p in people2
                        orderby p.Age
                        select p;
            var x = sort.ToArray();
            //Console.WriteLine(x[0].Name + " " + x[0].Age);
            stopWatch.Stop();
            linqTime += (int)stopWatch.ElapsedMilliseconds;
        }

        Console.WriteLine("IComparable: {0}", sortTime/repeats);
        Console.WriteLine("LINQ: {0}", linqTime/repeats);
        Console.WriteLine();
        size += size;
    }

    Console.WriteLine("Measurments done");
    Console.ReadLine();
}
Tworzone są kolejno 2 tablice z losowymi wartościami "Age" dla każdego obiektu person a następnie sortowane przy pomocy LINQ i Array.Sort wykorzystująca IComparer.

Okazało się że wydajność LINQ i Array.Sort zależała od tego, co było wcześniej!
Obie metody pracowały prawie tak samo długo, lecz sortowanie, które było pierwsze w kodzie (w tym przypadku sortowanie przy pomocy IComparera) przebiegało szybciej!

Jeśli by zamienić miejscami bloki kodu odpowiadające za sortowanie LINQ i Array.Sort:
            stopWatch.Start();
            var sort = from p in people2
                        orderby p.Age
                        select p;
            var x = sort.ToArray();
            //Console.WriteLine(x[0].Name + " " + x[0].Age);
            stopWatch.Stop();
            linqTime += (int)stopWatch.ElapsedMilliseconds;


            stopWatch.Start();
            Array.Sort(people1);
            stopWatch.Stop();
            sortTime += (int)stopWatch.ElapsedMilliseconds;

okazałoby się, że tym razem nieznacznie wygrywało LINQ!

Wszystko stało się jasne, gdy dopisałem parę linijek kodu zapisującego wyniki do pliku CSV i wygenerowałem wykresy w Excelu:

Porównanie czasu sortowania:



Stosunek czasów sortowania LINQ / IComparer





Jednak nie potrafię wyjaśnić czemu tak się dzieje...

Leniwe LINQ

Jak już wspomniałem w jednym z moich wcześniejszych postów, sortowanie danych możliwe jest za pomocą implementowania specjalnych interfejsów (IComparer, IComparable) i zapytań LINQ.

LINQ jest... leniwe. Jednak wbrew pozorom może być to pożądana cecha.
Znaczy to tyle, że nawet jeśli utworzymy zapytanie, to taka konstrukcja:
var sort = from p in people2
                       orderby p.Age
                       select p;
nie wykona tego zapytania i nie przypisze nam wyniku do zmiennej "sort".
Trzeba na to uważać zwłaszcza wtedy jeśli zależy nam na tym żeby zapytanie wykonało się natychmiast.

Jak zatem wymusić wykonanie takiego zapytania?

Wystarczy wykonać coś na naszej zmiennej "sort".
Dopiero kiedy  będziemy potrzebowali odwołać się do wyników zapytania, leniwe LINQ będzie zmuszone do pracy!

poniedziałek, 23 kwietnia 2012

Nazwy zmiennych w delegatach

Zastanawiałeś się kiedyś po co przy deklaracji delegata wymagane jest podawanie nazw zmiennych nawet jeśli nie są do niczego potrzebne?
Weźmy np prostego delegata przyjmującego 2 inty i zwracającego inta:

public delegate int SimpleDelegate(int x, int y);

Jeśli zdefiniujemy sobie takiego delegata gdzieś w programie to przecież nie używamy nigdzie tych zmiennych:

SimpleDelegate d = new SimpleDelegate(SomeFunction);

Więc czemu nie można zadeklarować delegata np w taki sposób? (kompilator nie pozwala):

public delegate int SimpleDelegate(int, int);

Okazuje się że powodem jest podpowiadanie składni przez IDE. Np przy zastosowaniu metody Invoke na delegacie widzimy nazwy parametrów wpisanych przy deklarowaniu delegata:


Taki wymóg na pewno pomaga w pracy. Czy nie lepiej jest zobaczyć coś takiego jak wyżej zamiast int SimpleDelegate(int, int) ?

niedziela, 22 kwietnia 2012

Sortowanie własnych obiektów po dowolnych polach

Czas na małe szaleństwo z interfejsami, łączeniem konstruktorów, przesłanianiem metod i metodami rozszerzającymi!

Wyobraźmy sobie, że mamy tablicę własnych obiektów np samochodów. Chcemy posortować te samochody raz po identyfikatorze, raz po prędkości maksymalnej, a jeszcze innym razem według koloru.
Jest to bardzo proste dzięki zastosowaniu interfejsu IComparer (zdefiniowany w System.Collections). Przy okazji skorzystamy z pozostałych wcześniej wymienionych technik.

Należy pamiętać że stosowanie interfejsu IComparer to tylko jedna z wielu możliwości posortowania obiektów. Można użyć LINQ, delegatów i anonimowych metod a nawet napisać własną funkcję sortującą.

Dzisiaj skoncentrujemy się jednak na IComparerze. Więc zaczynamy.

Klasa Car

W prostym projekcie konsolowym stworzyłem sobie nowy plik z klasą Car która na początku wyglądała tak:

    class Car
    {
        public int Id { get; set; }
        public int Vmax { get; set; }
        public string Color { get; set; }

        public Car() : this (1, 100, "Black") { }
        public Car(int Id, int Vmax, string Color)
        {
            this.Id = Id;
            this.Vmax = Vmax;
            this.Color = Color;
        }
    }

Mamy tutaj proste pola Id, Vmax, Color utworzone za pomocą skróconej wersji {get; set;}.
Dla przykładu zastosowane zostało też łączenie konstruktorów (constructor chaining). Domyślny bezparametrowy konstruktor przed wykonaniem swojej pracy uruchamia konstruktor "główny" który wykonuje całą pracę polegającą w tym przypadku na przypisaniu odpowiednich wartości do pól.

Utwórzmy zatem tablicę obiektów Car!
W głównej metodzie programu możemy napisać na przykład:

        static void Main(string[] args)
        {
            Car[] cars = {
                new Car(2, 200, "Green"),
                new Car(),
                new Car(3, 180, "Blue"),
                new Car(4, 190, "Yellow")
            };
            Console.ReadLine();
        }
    }

Teraz możemy wyświetlić tą tablicę.
Jednak napotkamy tutaj pewien problem. Chcielibyśmy mieć możliwość wyświetlenia całej tablicy za pomocą jednego wiersza kodu zamiast pisania na przykład pętli foreach w każdym miejscu gdzie wyświetlenie takiej tablicy byłoby potrzebne. Ponieważ jest to zwykła tablica własnych obiektów nie ma domyślnie żadnej metody wypisującej wszystkie samochody. Idealnym rozwiązaniem byłaby edycja klasy Array i dodanie odpowiedniej metody. Lecz czy możemy gdzieś edytować klasę Array? Raczej nie bardzo...

Z pomocą przychodzą nam metody rozszerzające.

Metody rozszerzające

Nawet jeśli edycja kodu jakiejś klasy jest niemożliwa (np jeśli korzystamy ze skompilowanej biblioteki). Możemy rozszerzyć jej funkcjonalność właśnie za pomocą metod rozszerzających.
Musimy utworzyć nową klasę z rozszerzeniami. Najlepiej wstawić ją do osobnego pliku w projekcie.
Klasa ta musi być statyczna i może wyglądać tak:

    static class Extensions
    {
        public static void DisplayArray(this Car[] cars)
        {
            foreach (Car c in cars)
            {
                Console.WriteLine(c.ToString());
            }
        }
    }

Wszystko opiera się o tajemniczy parametr wewnątrz nawiasów: DisplayArray(this Car[] cars).
Słowo kluczowe this jest użyte tutaj w takim kontekście aby poinformować kompilator że definiujemy właśnie metodę rozszerzającą, która ma być zastosowana do tablicy typu Car!

Jeśli teraz chcielibyśmy wyświetlić całą tablicę samochodów możemy to zrobić bardzo prosto.

        static void Main(string[] args)
        {
            Car[] cars = {
                new Car(2, 200, "Green"),
                new Car(),
                new Car(3, 180, "Blue"),
                new Car(4, 190, "Yellow")
            };
            cars.DisplayArray();
            Console.ReadLine();
        }

Zwróćmy uwagę, że gdy po kropce zaczynamy wpisywać nazwę funkcji DisplayArray() to IntelliSense już widzi naszą metodę i dodatkowo oznacza ją niebieską strzałką, która oznacza że jest to właśnie metoda rozszerzająca:

Jeszcze tylko przeciążenie metody ToString() na obiekcie Car i możemy działać dalej:

    class Car
    {
        public int Id { get; set; }
        public int Vmax { get; set; }
        public string Color { get; set; }

        public Car() : this (1, 100, "Black") { }
        public Car(int Id, int Vmax, string Color)
        {
            this.Id = Id;
            this.Vmax = Vmax;
            this.Color = Color;
        }

        public override string ToString()
        {
            return String.Format("ID: {0}\tVmax: {1}\tColor: {2}", Id, Vmax, Color);
        }
    }

Ponieważ każdy obiekt zawiera definicję metody ToString() musimy ją przeciążyć używając słowa kluczowego override.
Po skompilowaniu i uruchomieniu programu widzimy pierwsze rezultaty:

Sortowanie

Nareszcie przechodzimy do głównego problemu: sortowania obiektów.
Będziemy posługiwać się funkcją statyczną Array.Sort()
Jest ona wielokrotnie przeciążana więc możemy podać tutaj 2 parametry. Pierwszym parametrem będzie oczywiście nasza tablica do posortowania a drugim parametrem będzie obiekt implementujący interfejs IComparer.

Tworzymy zatem nowy plik np CarComparers.cs a w nim definicje trzech klas:

    class CarColorComparer : IComparer
    {
        int IComparer.Compare(object o1, object o2)
        {
            Car t1 = o1 as Car;
            Car t2 = o2 as Car;
            if (t1 != null && t2 != null)
                return String.Compare(t1.Color, t2.Color);
            else
                throw new ArgumentException("Parameter is not a Car!");
        }
    }
    class CarIdComparer : IComparer
    {
        int IComparer.Compare(object o1, object o2)
        {
            Car t1 = o1 as Car;
            Car t2 = o2 as Car;
            if (t1 != null && t2 != null)
            {
                return t1.Id.CompareTo(t2.Id);
            }
            else
                throw new ArgumentException("Parameter is not a Car!");
        }
    }
    class CarVmaxComparer : IComparer
    {
        int IComparer.Compare(object o1, object o2)
        {
            Car t1 = o1 as Car;
            Car t2 = o2 as Car;
            if (t1 != null && t2 != null)
            {
                if (t1.Vmax > t2.Vmax) return 1;
                else if (t1.Vmax < t2.Vmax) return -1;
                else return 0;
            }
            else
                throw new ArgumentException("Parameter is not a Car!");
        }
    }

Każda klasa implementuje interfejs IComparer więc musi posiadać metodę Compare przyjmującą dwa obiekty. W każdej metodzie rzutujemy je na typ Car. Jeśli któryś z obiektów nie jest typem Car to zostanie wyrzucony wyjątek.
Metoda Compare powinna zwracać 1 jeśli pierwszy obiekt jest "większy" od drugiego, -1 gdy jest "mniejszy" a 0 gdy oba obiekty traktujemy na równi.
W przypadku porównywania koloru czyli typu string możemy zaoszczędzić kodowania wywołując metodę String.Compare(t1.Color, t2.Color);
W przypadku inta w jednej metodzie wywołaliśmy funkcję CompareTo() a w drugiej zaimplementowaliśmy własne porównywanie.

Moglibyśmy użyć już konstrukcji Array.Sort(cars, new CarIdComparer()); jednak zrobimy to bardziej elegancko uniezależniając się od znajomości klas obiektów porównujących.

Dodajmy do klasy Car odpowiednie pola statyczne zwracające odpowiednie obiekty porównujące tym samym uzyskując ostateczną wersję klasy Car:


    class Car
    {
        public int Id { get; set; }
        public int Vmax { get; set; }
        public string Color { get; set; }

        public Car() : this (1, 100, "Black") { }
        public Car(int Id, int Vmax, string Color)
        {
            this.Id = Id;
            this.Vmax = Vmax;
            this.Color = Color;
        }

        public static IComparer SortById
        { get { return (IComparer)new CarIdComparer(); } }

        public static IComparer SortByVmax
        { get { return (IComparer)new CarVmaxComparer(); } }

        public static IComparer SortByColor
        { get { return (IComparer)new CarColorComparer(); } }

        public override string ToString()
        {
            return String.Format("ID: {0}\tVmax: {1}\tColor: {2}", Id, Vmax, Color);
        }
    }

Pola zawierają jedynie gettery zwracające obiekty implementujące odpowiednie porównania, czyli to o co nam chodziło.

Teraz aby przetestować działanie programu w głownej metodzie napiszmy:

        static void Main(string[] args)
        {
            Car[] cars = {
                new Car(2, 200, "Green"),
                new Car(),
                new Car(3, 180, "Blue"),
                new Car(4, 190, "Yellow")
            };
            Console.WriteLine("Default order:");
            cars.DisplayArray();
            Console.WriteLine();

            Console.WriteLine("Sorted by ID:");
            Array.Sort(cars, Car.SortById);
            cars.DisplayArray();
            Console.WriteLine();

            Console.WriteLine("Sorted by Vmax:");
            Array.Sort(cars, Car.SortByVmax);
            cars.DisplayArray();
            Console.WriteLine();

            Console.WriteLine("Sorted by Color:");
            Array.Sort(cars, Car.SortByColor);
            cars.DisplayArray();
            Console.WriteLine();

            Console.ReadLine();
        }

Dokładając trochę pracy do zdefiniowania obiektów porównujących otrzymaliśmy eleganckie sortowanie po wielu właściwościach:

sobota, 21 kwietnia 2012

Metadane i manifest pakietu

Można pisać już dość rozbudowane aplikacje w C# i nie zastanawiać się nad tym co to są metadane czy manifest pakietu, ponieważ taka wiedza na ogół (zwłaszcza na początku) nie jest potrzebna.
Lecz chcąc "wyryć C# na blachę od dechy do dechy", trzeba znać wszystko!

Na początku sam miałem problemy z rozróżnieniem metadanych od manifestu. Wiedziałem że oba niosą jakieś wiadomości opisujące pakiet (czyli to co wypluwa kompilator np .dll lub .exe) ale co konkretnie?

Metadane

Metadane opisują w szczegółowy sposób cechy każdego "typu" zamieszczonego w pliku binarnym. Jeśli mamy np klasę o nazwie SportowySamochód, to metadane będą zawierać takie informacje jak:
  • klasa bazowa klasy SportowySamochód (np Samochód)
  • interfejsy implementowane przez klasę (jeśli jakieś są)
  • pełny opis każdej składowej obsługiwanej przez typ SportowySamochód (pola, funkcje, właściwości)
Metadane zawsze znajdują się w pakiecie i są automatycznie generowane przez kompilator.

Manifest

Manifest pakietu to inaczej własne metadane. Stąd bierze się pewna nieścisłość ponieważ to także są metadane... tylko że własne. Oznacza to tyle że zawiera bardziej "osobiste" informacje takie jak:
  • bieżąca wersja pakietu (numer wersji)
  • nazwa modułu
  • informacje o kulturze (służące do lokalizacji plików tekstowych i graficznych np jeśli tworzymy aplikację wielojęzyczną)
  • listę wszystkich innych pakietów niezbędnych do poprawnego działania bieżącego
  • informacje o prawach autorskich

Po co komu metadane?
Tobie :) Chociażby do korzystania z funkcji IntelliSense (automatyczne podpowiadanie składni bardzo przyspieszające kodowanie). Także kompilator JIT (Just In Time) korzysta z metadanych podczas tłumaczenia kodu pośredniego CIL (lub IL, czy MSIL jak kto woli) na kod maszynowy dla danej architektury.

Podglądanie metadanych prostej aplikacji C#

Spróbujemy podejrzeć teraz metadane w VisualStudio2010 najprostrzej aplikacji:
Poprostu stworzony został nowy projekt konsolowej aplikacji i dodane zostały linijki zaczynające się od Console.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Simple program just to compile package.");
            Console.ReadLine();
        }
    }
}

Po skompilowaniu projektu należy odpalić narzędzie ildasm. W tym celu w VisualStudio na górnej belce klikamy kolejno Tools -> ILDasm lub Tools -> Visual Studio Command Prompt i wpisujemy ildasm i naciskamy Enter.
W nowo otwartym oknie klikamy File -> Open i wybieramy skompilowany plik .exe. Naszym oczom ukaże się taki widok:

Jak na chwilę obecną nie wiele zobaczymy ponieważ nasz program jest tak prosty że nie zawiera wielu metadanych. Aby kompilator wygenerował więcej metadanych należało by stworzyć jakiś obiekt i dodać do niego parę metod czy właściwości. Można natomiast prześledzić kod CIL glównej metody Main klikając na nią dwukrotnie na wygenerowanym drzewie.

Niestety ildasm pozwala jedynie na przeglądanie czystego kodu CIL zamiast C# lub Visual Basic. Aby podejrzeć zdeasemblowany kod w naszym zarządzanym języku trzeba sięgnąć po inne narzędzie, np Reflector. 30-dniową wersję trial można pobrać ze strony:
http://www.reflector.net/