Την τελευταία εβδομάδα παρακολούθησα ένα σεμινάριο της Microsoft, ονόματι "Whidbey Combined Course".
Βασικά, ήταν ένα γενικότερο introduction στα νέα features του Visual Studio 2005 και του .NET 2.0.
Ένα απο τα Labs, είχε να κάνει με language enhancements, και επικεντρωνόταν στα Generics.
Το Lab ήταν απλό. Δημιουργούσες ένα Stack class, χρησιμοποιώντας το γενικό object type σαν τύπο παραμέτρου για τις μεθόδους του Stack ( Push(object obj) & object Pop(); ), και ένα ακόμη Stack implementation, χρησιμοποιώντας Generics.
Ένα test προγραμματάκι που είχε μαζί, καλούσε τις μεθόδους 2 instances, μια απο κάθε τύπο Stack, και χρονομετρούσε πόσο θα χρειαστεί για να κάνει Push() & Pop() μερικές χιλιάδες int's.
Το αποτέλεσμα ήταν εντυπωσιακό ! Το Generics implementation ήταν πολύ πιο γρήγορο. Το Lab τελείωνε λοιπόν με τη φράση "the generics implementation is almost an order of magnitude faster than a regular stack".
Αυτό που είδα με εντυπωσίασε πάρα πολύ, αρκετά για να το ψάξω λίγο περισσότερο στο διάλειμμα. Παρακάτω έχω τα 2 Stack sources:
using System;
using System.Collections.Generic;
using System.Text;
namespace GenericsTest
{
class RegularStack
{
private int _size = 0;
private object[] _elements = new object[0];
private int _currentIndex = 0;
public RegularStack(int size) {
this._size = size;
this._elements = new object[_size];
}
public void Push(object obj)
{
this._elements[_currentIndex] = obj;
_currentIndex++;
}
public object Pop()
{
return this._elements[--_currentIndex];
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace GenericsTest
{
class GenericStack<T>
{
private int _size = 0;
private T[] _elements = new T[0];
private int _currentIndex = 0;
public GenericStack(int size)
{
this._size = size;
this._elements = new T[_size];
}
public void Push(T obj)
{
this._elements[_currentIndex] = obj;
_currentIndex++;
}
public T Pop()
{
return this._elements[--_currentIndex];
}
}
}
και το main προγραμματάκι που τα χρονομετρεί:
using System;
using System.Collections.Generic;
using System.Text;
namespace GenericsTest
{
class Program
{
public delegate void TimedMethod();
public static long ExecuteTimer(TimedMethod _method) {
long startTicks = DateTime.Now.Ticks;
_method();
long totalRunTicks = (DateTime.Now.Ticks - startTicks);
return (totalRunTicks / TimeSpan.TicksPerMillisecond);
}
public static void UseRegular() {
RegularStack _Stack = new RegularStack(repetitions);
for (int i = 0; i < repetitions; i++)
{
_Stack.Push(i);
}
for (int i = 0; i < repetitions; i++)
{
int retVal = (int)_Stack.Pop();
}
}
public static void UseGeneric()
{
GenericStack<int> _Stack = new GenericStack<int>(repetitions);
for (int i = 0; i < repetitions; i++)
{
_Stack.Push(i);
}
for (int i = 0; i < repetitions; i++)
{
int retVal = _Stack.Pop();
}
}
public static int repetitions = 10000000;
static void Main(string[] args)
{
long regTime = ExecuteTimer(new TimedMethod(UseRegular));
long genTime = ExecuteTimer(new TimedMethod(UseGeneric));
Console.WriteLine("Regular Stack Time {0} - Generics Stack Time {1}", regTime, genTime);
Console.ReadLine();
}
}
}
Τρέχοντας το πρόγραμα, βλέπουμε:
Regular Stack Time 2015 - Generics Stack Time 234
.. πράγματι, φαίνεται οτι το GenericStack είναι όντως 10 φορές πιο γρήγορο. Οπότε αποφάσισα να αλλάξω λίγο τον κώδικα, χρησιμοποιώντας strings σαν παραμέτρους για τα 2 Stacks.
public static void UseRegular() {
RegularStack _Stack = new RegularStack(repetitions);
for (int i = 0; i < repetitions; i++)
{
_Stack.Push(i.ToString());
}
for (int i = 0; i < repetitions; i++)
{
string retVal = (string)_Stack.Pop();
}
}
public static void UseGeneric()
{
GenericStack<string> _Stack = new GenericStack<string>(repetitions);
for (int i = 0; i < repetitions; i++)
{
_Stack.Push(i.ToString());
}
for (int i = 0; i < repetitions; i++)
{
string retVal = _Stack.Pop();
}
}
.. τώρα, τρέχοντας το πρόγραμμα, βλέπουμε άλλα :)
Regular Stack Time 6484 - Generics Stack Time 6812
Το δοκίμασα με διάφορους τύπους. Εκτός απο αριθμιτικά, το Generics implementation είναι πάντα λίγο πιο αργό απο το απλό. Ο antonch που έκανε το σεμινάριο μου εξήγησε μετά οτι είναι φυσικά πιο γρήγορο το generic implementation γιατί σώζει εσωτερικά στη μνήμη ένα Int32 κάθε φορά, ενώ το regular stack ένα object. Σε κάθε άλλη περίπτωση, οι χρόνοι τους θα είναι παρόμοιοι με το generic παντα λίγο πιο αργό ...
Angel
O:]