Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια
σε

 

Αρχική σελίδα Ιστολόγια Συζητήσεις Εκθέσεις Φωτογραφιών Αρχειοθήκες

3 τρόποι να κάνεις new ...

Îåêßíçóå áðü ôï ìÝëïò anjelinio. Τελευταία δημοσίευση από το μέλος Aris στις 24-10-2005, 16:21. Υπάρχουν 10 απαντήσεις.
Ταξινόμηση Δημοσιεύσεων: Προηγούμενο Επόμενο
  •  20-10-2005, 14:50 6289

    3 τρόποι να κάνεις new ...

    Με όλη τη χαρά τελευταία με το ΑΟΡ κι όλα τα σχετικά νέα 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:]
  •  20-10-2005, 14:51 6290 σε απάντηση της 6289

    Απ: 3 τρόποι να κάνεις new ...

    ... πάλι χάλια, μου τα'κανε ο editor γαμώτο :(
    Angel
    O:]
  •  20-10-2005, 16:15 6291 σε απάντηση της 6289

    Απ: 3 τρόποι να κάνεις new ...

     anjelinio wrote:

    Οκ, ο δεύτερος είναι με χρήση του Activator. Να κι οι κώδικες:

    static MyObject CreateByActivator(){
    ObjectHandle obj = Activator.CreateInstance("Tests", "Tests.MyObject");
    return (MyObject) obj.Unwrap();
    }

     

    Εγω σε ενα σχετικα "περιεργο" project μου (δες στο Blog μου αν θελεις να δεις περι τινος προκειται), χρησιμοποιω σε καποιο σημειο αυτο που εγραψες πιο πανω.

    Εκει που το χρησιμοποιω ομως, ο χρονος που κανει δεν με απασχολει και πραγματικα μου λυνει τα χερια ετσι οπως εχω "στησει" το application, ασε που δεν "φαινεται" η σχετικη καθυστερηση.

    Σε αλλες περιπτωσεις ομως π.χ. loops κτλ, μαλλον θα χρειαζεται αλλο design, εκτος βεβαια, οπως ειπα και πιο πανω, αν ειναι σε σημειο που δεν σε πολυ-πειραζει...


    Software Engineer, specializes in Microsoft .net/C#, COM, Sql Server and now Python.
  •  20-10-2005, 18:49 6296 σε απάντηση της 6289

    Απ: 3 τρόποι να κάνεις new ...

    Πολύ καλό... κι εγώ είχα πάντα την περιέργεια... Αλλά δεν περίμενα με reflection να βγάζει μηδέν...
    Χρήστος Γεωργακόπουλος
  •  21-10-2005, 09:00 6305 σε απάντηση της 6296

    Απ: 3 τρόποι να κάνεις new ...

    H ταχύτητα είναι ένας από τους παράγοντες που μας οδηγούν τις επιλογές... Έτσι, για να γίνεται κουβέντα, πως τους χρησιμοποιείτε αυτούς τους τρόπους μέσα στις εφαρμογές σας; Τι προβλήματα σας λύνουν; Να υποθέσω ότι υπάρχουν ανάλογα σενάρια που είναι προτιμότερο να χρησιμοποιήσουμε κάποιον από αυτούς τους τρόπους, πέραν του πρώτου;
    Vir prudens non contra ventum mingit
  •  21-10-2005, 10:53 6308 σε απάντηση της 6305

    Απ: 3 τρόποι να κάνεις new ...

     KelMan wrote:
    H ταχύτητα είναι ένας από τους παράγοντες που μας οδηγούν τις επιλογές... Έτσι, για να γίνεται κουβέντα, πως τους χρησιμοποιείτε αυτούς τους τρόπους μέσα στις εφαρμογές σας; Τι προβλήματα σας λύνουν; Να υποθέσω ότι υπάρχουν ανάλογα σενάρια που είναι προτιμότερο να χρησιμοποιήσουμε κάποιον από αυτούς τους τρόπους, πέραν του πρώτου;


    ... τυπικά, εγώ χρησιμοποιώ το Dynamic CLass Loading οπουδήποτε μπορώ να .. "σφίξω" pluggable functionality. Π.χ Database providers, αλλά γενικότερα ... Providers. Συνήθως "κρύβω" τις κλάσσεις μου πίσω απο ένα interface, και χρησιμοποιώντας ένα σχετικά τυποποιημένο πλέον Factory class και λίγη Xml, επιστρέφω  implementations του εκάστοστε provider που τις ορίζω στο Xml.

    Ως τώρα χρησιμοποιούσα τον Activator κατα κόρον λόγω ... ευκολίας, και κυρίως επειδή πρέπει να ξανα-ανακαλύψεις τον τροχό αν επιχειρήσεις να υλοποιήσεις το functionality του Activator μόνος σου. Τώρα σκέφτομαι να ρίξω μια ματιά περισσότερο όταν βρώ το χρόνο στο reflection, κι ας φοβάμαι oτι τελικά .. θα γράψω πάλι τον Activator με άλλο όνομα :P (Reflectivator ίσως?!?!)

    Χρειάζεται testing και πειραματισμό αυτή η ιστορία, και δυστυχώς τρέχουμε σαν τρελλοί στη δουλειά προς το παρόν για να πείς οτι έχεις το χρόνο και την όρεξη να παίξεις ... τέλος πάντων όμως, όταν πέφτεις σε μια λίμνη είναι μάταιο να αναρωτιέσαι αν τελικά ήθελες να μάθεις να κολυμπάς - είσαι ήδη μέσα !!!  Οπότε ...  θα το κοιτάξω όταν βρώ περισσότερο χρόνο σε πιο βάθος, εκτός κι αν το κοιτάξει κανένας άλλος πριν απο 'μένα :)

    Καλημέρα μας

    Angel
    O:]
  •  21-10-2005, 11:02 6309 σε απάντηση της 6305

    Απ: 3 τρόποι να κάνεις new ...

     KelMan wrote:
    H ταχύτητα είναι ένας από τους παράγοντες που μας οδηγούν τις επιλογές... Έτσι, για να γίνεται κουβέντα, πως τους χρησιμοποιείτε αυτούς τους τρόπους μέσα στις εφαρμογές σας; Τι προβλήματα σας λύνουν; Να υποθέσω ότι υπάρχουν ανάλογα σενάρια που είναι προτιμότερο να χρησιμοποιήσουμε κάποιον από αυτούς τους τρόπους, πέραν του πρώτου;

    Εγώ έχω ένα μεγάλο αριθμό παραμετρικών πινάκων που χειρίζονται από ένα caching σύστημα. Αυτό μέσω web service ζητάει τον πίνακα που θέλει ο χρήστης. Αν έχω 100 παραμετρικούς πίνακες, θέλω 100 web methods στα web services, 100 ρουτίνες στο DAL για να παίρνουν τα δεδομένα από τη βάση, 100 ρουτίνες στο DAL για να φέρνουν την ημερομηνία τελευταίας τροποποίησης. Σε όλες αυτές ρισκάρω να έχω λάθη από τους developers τα οποία μπορεί να μην φανούν απ' ευθείας στην εφαρμογή λόγω του caching. Οπότε αντί των 300 και βάλε ρουτινών που χρειάζομαι (έχει αντίστοιχα πράγματα και στον client), έχω μία από κάθε είδος η οποία γίνεται initiate δυναμικά απλά δίνοντας το όνομα του πίνακα. Έτσι το caching περνάει ανώδυνα στους developers οι οποίοι όταν έχουν ένα νέο παραμετρικό πίνακα απλά φτιάχουν το αντίστοιχο typed dataset και ζητάνε τα δεδομένα από το caching σύστημα μόνο με το όνομα του πίνακα. Είναι πραγματικά ...θεόσταλτη λύση...


    Χρήστος Γεωργακόπουλος
  •  21-10-2005, 11:54 6312 σε απάντηση της 6309

    Απ: 3 τρόποι να κάνεις new ...

    Οκ ... νομίζω οτι μόλις ανέβασα το status μου απο "ψυχάκιας" σε "αρρωστάκι" :D

    ΟΙ ΚΩΔΙΚΕΣ:

    using System;

    using System.Reflection;

    using System.Runtime.Remoting;

     

    namespace VariousTests

    {

           /// <summary>

           /// Summary description for Class1.

           /// </summary>

           class Class1

           {

                  /// <summary>

                  /// The main entry point for the application.

                  /// </summary>

                  [STAThread]

                  static void Main(string[] args)

                  {

                         TestInstantiation();

                         Console.ReadLine();

                  }

     

           public delegate object CreateInstance(string className, string assemblyName);

     

           public static void TestInstantiation(){

                  string className = "TestGeneric.SecondLevelChild";

                  string assemblyName = "TestGeneric";

     

                  long repetitions = 1000000;

                  long totalTicks = 0;

                  for(int i=0; i<repetitions; i++){

                         totalTicks += CountMethod(new CreateInstance(CreateWithActivator), className, assemblyName);

                  }

                  long avgTicksActivator = totalTicks/repetitions;

     

                  totalTicks = 0;

                  for(int c=0; c<repetitions; c++){

                         totalTicks += CountMethod(new CreateInstance(CreateWithReflection), className, assemblyName);

                  }

                  long avgTicksReflection = totalTicks/repetitions;

     

                  Console.WriteLine("Activator average for {0} instances: {1}", repetitions,  avgTicksActivator);

                  Console.WriteLine("Reflection average for {0} instances: {1}", repetitions,  avgTicksReflection);

           }

     

           public static long CountMethod(CreateInstance method, string className, string assemblyName){

                  long startTicks = DateTime.Now.Ticks;

                  object obj = method(className, assemblyName);

                  long endTicks = DateTime.Now.Ticks;

     

                  return (endTicks-startTicks);

           }

     

           public static object CreateWithActivator(string className, string assemblyName){

                  ObjectHandle handle = Activator.CreateInstance(assemblyName, className);

                  return handle.Unwrap();

           }

     

           public static object CreateWithReflection(String className, string assemblyName){

                  Assembly assembly = Assembly.LoadWithPartialName(assemblyName);

                  return assembly.CreateInstance(className, false);

           }

    }

    }

     

    .. λοιπόν, αν δεν έχω κάνει κάπου λάθος στον παραπάνω κώδικα, τα αποτελέσματα είναι τα εξής:

    Με 1000 instantiations, Activator: 1560 ticks, Reflection: 0 ticks
    Με 1000000 instantiations, Activator: 220 ticks, Reflection: 189 ticks

    Όπως καταλαβαίνετε, όσο περισσότερα consecutive instantiations κάνουμε, τόσο η απόδωση του Activator βελτιώνεται και τείνει να φτάσει στην ταχύτητα του Reflection.

    Και μετά απο όλα αυτά, αναρωτιέμαι εγώ ... μήπως πρέπει τώρα να κάνω Find & Replace όλα τα Activator σε ότι κώδικα έχω γράψει ως τώρα, και να τ'αντικαταστήσω με Reflection calls?

    Πολύ καλή μέρα μας .. ας δουλέψουμε και λίγο τώρα ( αχ ... πάντα προτιμούσα αυτο το πρώτο γράμμα στο R'n'D ... ;] )
    Angel
    O:]
  •  21-10-2005, 18:32 6334 σε απάντηση της 6312

    Απ: 3 τρόποι να κάνεις new ...

    Δοκίμασα τον κώδικα, με μία αλλαγή. Φτιάχνω System.IntPtr (assembly mscorlib).

    Οι κλήσεις με Activator είνα μεν αργότερες (195 με 165 είναι τυπικό αποτέλεσμα), αλλά όχι τραγικά.
    Η διαφορά οφείλεται στο "βάρος" του ObjectHandle και στην κλήση της Unwrap().

    Άρης


    Aris
  •  22-10-2005, 13:45 6347 σε απάντηση της 6334

    Απ: 3 τρόποι να κάνεις new ...

    Πιστεύω -αν και όχι φανατικά - οτι η απόδωση του activator έχει πολύ να κάνει με:

    1. Το πόσο συχνά ζητάμε νέο instance (έχει ή δεν έχει κάνει expire η όποια cache διατηρεί εσωτερικά)
    2. Τι "ποικιλία" παρουσιάζουν τα instances που του ζητάμε να φτιάξει

    Ένα πιο ρεαλιστικό τέστ θα περίμενε λίγο πρωτού ζητήσει το επόμενο αντικείμενο, και θα ζητούσε αντικείμενα τυχαίων τύπων, απο διάφορα assemblies. Αλλά προς το παρόν .. είναι σαββατοκύριακο !!! :D
    Angel
    O:]
  •  24-10-2005, 16:21 6391 σε απάντηση της 6347

    Απ: 3 τρόποι να κάνεις new ...

    Ευτυχώς που δεν το πιστεύεις φανατικά ... Big Smile [:D]

    Εφ' όσον εξετάζουμε την διαφορά ταχύτητας Activator vs. Reflection, πρέπει - κατά την γνώμη μου - να εξασφαλίσουμε ότι:
    - το assembly που περιέχει τα types είναι στην μνήμη (= δεν έχουμε χρήση δίσκου)
    - κάνουμε κάποιο memory allocation (= ώστε να υπάρχει instance "data segment")
    - ζητάμε το ίδιο type

    Η διαφορά οφείλεται στο ότι η κλήση της Assembly.CreateInstance δημιουργεί ένα instance του τύπου ενώ η κλήση της Activator.CreateInstance δημιουργεί ένα instance του τύπου και ένα marshalByRef object για remoting.

    Άρης


    Aris
Προβολή Τροφοδοσίας RSS με μορφή XML
Με χρήση του Community Server (Commercial Edition), από την Telligent Systems