Με όλη τη χαρά τελευταία με το ΑΟΡ κι όλα τα σχετικά νέα trends, αναρωτηθήκατε ποτέ τι κόστος έχει να κάνεις instantiate objects δυναμικά, στο runtime;
Τον τελευταίο καιρό χρησιμοποιώ αρκετά το Dunamic Class Loading, χρησιμοποιώντας το Activator.CreateInstance(...)
. Κατανοώ οτι έτσι βάζω ένα κάποιο επιπλέον overhead στην εφαρμογή μου, αλλά ως τώρα, το θεωρούσα ένα λογικό tradeoff σε σχέση με την ευελιξία που κέρδιζα.
Πριν μερικές μέρες, σκέφτηκα οτι θα μπορούσα ίσως να πάρω τον default no-args constructor ενός τύπου, και να τον καλέσω με Reflection αντικαθιστώντας έτσι τα Activator.CreateInstance(...)
calls. Αναρωτήθηκα τι διαφορά στο performance θα έχουν οι δύο τρόποι, και τι διαφορά θα έχουν επίσης με ένα κανονικό instantiation;
Έτσι, έγραψα ένα μικρό Console App για να δοκιμάσω να κάνω συγκρίσεις μεταξύ των 3 τρόπων. Έγραψα 3 μεθόδους, μια για κάθε τρόπο instantiation.
O πρώτος είναι ο κανονικός, κλασσικός τρόπος:
static MyObject CreateDirect() {
return new MyObject();
}
… Πυρηνική Φυσική ε;
Οκ, ο δεύτερος είναι με χρήση του Activator. Να κι οι κώδικες:
static MyObject CreateByActivator(){
ObjectHandle obj = Activator.CreateInstance("Tests", "Tests.MyObject");
return (MyObject) obj.Unwrap();
}
Λίγο πιο σύνθετο, αλλά όχι κάτι τρελλό. Επίσης, χρησιμοποιεί το System.Runtime.Remoting namespace.
Ο τρίτος τρόπος είναι να πάρω τις λεπτομερειες του constructor απο το Type, και να τον καλέσω με Invoke(...). Source:
static MyObject CreateByReflection(){
Type myType = typeof(MyObject);
ConstructorInfo constr = myType.GetConstructor(new System.Type[0]);
return (MyObject) constr.Invoke(new object[0]);
}
Πολύ όμορφα ως εδω ... να και η Main(args[]) η οποία τρέχει και τις τρείς μεθόδους, και γράφει τους χρόνους καθεμιάς:
static void Main(string[] args)
{
long startTicks, endTicks;
// Create directly first ...
startTicks = DateTime.Now.Ticks;
MyObject obj = CreateDirect();
endTicks = DateTime.Now.Ticks;
Console.WriteLine("Called direct. Ticks: {0}", (endTicks-startTicks));
// Create by activator ...
startTicks = DateTime.Now.Ticks;
obj = CreateByActivator();
endTicks = DateTime.Now.Ticks;
Console.WriteLine("Called by activator. Ticks: {0}", (endTicks-startTicks));
// Create by reflecting constructor ...
startTicks = DateTime.Now.Ticks;
obj = CreateByReflection();
endTicks = DateTime.Now.Ticks;
Console.WriteLine("Called by reflection. Ticks: {0}", (endTicks-startTicks));
Console.ReadLine();
}
Όμορφα
Αυτό είναι το output στο μηχάνημά μου (PIII 2.7Ghz, 1Gb RAM).
Called direct. Ticks: 0
Called by activator. Ticks: 156258
Called by reflection. Ticks: 0
Εδώ φρίκαρα λίγο ... αν δεχτώ τα παραπάνω, κάθε φορά που χρησιμοποιώ τον Activator είμαι περίπου ... 156258 φορές πιο αργός απο ένα direct instatiation !!! Κι ακόμα .. αν χρησιμοποιήσω Reflection, είμαι ΕΞΙΣΟΥ γρήγορος με το direct instantiation !!?!?!?
Ok .. μετά απο λίγη σκέψη, κατέληξα στο ότι η μέθοδός μου η οποία χρησιμοποιεί Reflection γλυτώνει ένα μεγάλο κόπο, να βρεί το Type απο ένα string, όπως κάνει ο Activator. Οπότε για να είμαι "τίμιος", άλλαξα τον κώδικά της σε:
static MyObject CreateByReflection2(){
Type myType = Type.GetType("Tests.MyObject");
ConstructorInfo constr = myType.GetConstructor(new System.Type[0]);
return (MyObject) constr.Invoke(new object[0]);
}
Μετά απο μερικά trial runs, παρατήρησα ένα πολύ περίεργο φαινόμενο. Ο Activator κάθε τόσο επιστρέφει σε πάνω-κάτω 156280 ticks, αλά τις περισσότερες φορές μετα την πρώτη κλήση επιστρέφει σε 0 ticks. Επίσης, το Reflection επιστρέφει σε 0 πάντα.
Για να το ψάξω λίγο ακόμη περισσότερο, έβαλα τον κώδικα της Main σε μια άλλη μέθοδο, και στη Main κάνω ένα loop που την καλεί μερικές φορές, για να έχω ένα καλύτερο "δείγμα" απο trial runs. Παραθέτω ένα μικρό κομμάτι απ'το output (το loop έκανε τουλάχιστον 100 φορές αυτήν την ιστορία) και παρατηρήστε το pattern του Activator. Αυτό επαναλαμβάνεται αρκετές φορές στο ολόκληρο output που πήρα.
Called direct. Ticks: 0
Called by activator. Ticks: 156247
Called by reflection. Ticks: 0
Called direct. Ticks: 0
Called by activator. Ticks: 0
Called by reflection. Ticks: 0
Called direct. Ticks: 0
Called by activator. Ticks: 0
Called by reflection. Ticks: 0
...
Μετά την πρώτη κλήση, ο Activator επιστρέφει σε 0 ticks για κάποιο χρονικό διάστημα. Υποπτεύομαι οτι κάπως/κάπου cache-άρει τις πληροφορίες που χρειάζεται για το instantiation, και αρχίζει πάλι απ'την αρχή μετά απο λίγο, όταν αυτές οι πληροφορίες γίνονται dropped - αλλά αυτό είναι απλώς μια θεωρία μου και μάλιστα όχι και πολύ ... βάσιμη, γιατί ήταν πρωί όταν το σκεφτόμουν :D
Επίσης μυστήριο έχει και το Reflection που επιστρέφει σε 0 ticks. Ακόμη κι αν είναι τόσο γρήγορο έτσι βέβαια, χάνει σε ευελιξία λόγω του ότι δεν μπορώ (ακόμα) να του πώ απο ποιό assembly να φορτώσει το αντικείμενο που θέλω. Σε αυτό το σημείο υστερεί απ'τον Activator, αλλά .. τι να κάνεις ... :)
Σκέψεις και θεωρίες επι των παραπάνω, όπως πάντα πολύ ευπρόσδεκτες !
Πολύ καλή μέρα μας
Angel
O:]