Start Info Community Spielen
 
 

Morgengrauner Dokumentation

Dateipfad: /home/mud/mudlib/doc/concepts/effizienz

Effizienz
 BESCHREIBUNG:
     Effizienz in der Programmierung ist leider nicht ganz so einfach zu
     beschreiben, da es viel mit der zugrundeliegenden Verarbeitung der
     Programme zu tun hat. Es geht ganz gut am Beispiel.

     Generell haben Lesbarkeit und Wartbarkeit von Code Vorrang vor dessen
     Effizienz, gerade weil die wirklich arbeitslastigen Methoden in der Lib
     stecken. Ausserdem ist es im Allgemeinen nicht empfehlenswert, (viel)
     Aufwand in die Optimierung von Code zu stecken, solange nicht klar ist,
     dass dies ueberhaupt notwendig ist.
     Les-/Wartbarkeit und effizienter Stil schliessen sich aber nicht aus und
     einige (einfache) Grundregeln lassen sich einfach einhalten.

     Fuer diejenigen unter euch, die gerade erst mit LPC zusammenstossen
     gibt es (*) an den besonders wichtigen Stellen. Auf Dauer solltet ihr
     aber mal alle Eintraege ueberfliegen. Den ersten koennen alle hier
     beherzigen:

     LPC wird beim Laden nicht optimiert:
      Das was ihr schreibt, wird auch so ausgefuehrt, es werden keine
      Schleifen optimiert, keine unnoetigen Zuweisungen entfernt, nichts
      wird veraendert:
      - ueberlegt euch also euren Code gut, wenn er an kritischen Stellen
        steht oder sehr viel Rechenzeit kostet (zB geschachtelte Schleifen)
      - testet einfach mal Varianten und fragt auf -lpc nach Optimierung!

     call_out und heart_beat erzeugen konstante Last:
      Jeder call_out() steht in einer Liste, die im selben Takt wie der
      heart_beat() durchsucht wird. Beides kostet Zeit. Beide Methoden
      verhindern zudem das Ausswappen des entsprechenden Objektes. Deshalb
      schalten sich Raummeldungen (AddRoomMessage funktioniert ueber
      call_out()) und der heart_beat() von /std/npc nach dem Verlassen des
      Raumes durch den letzten Spieler selbst aus.
 *    - bitte achtet darauf, unnoetige call_out/heart_beat zu vermeiden.
        (Insbesondere sich bewegende NPCs sollten sich auch irgendwann
         wieder abschalten - es gibt einen funktionierenden MNPC mit diesen
         Eigenschaften unter /p/service/padreic/mnpc.)
      - fuer regelmaessige Aufrufe in einem Objekt, wo der genaue Zeitpunkt
        nicht auf einige Sekunden ankommt,  bietet sich auch reset() mit
        set_next_reset() an
      - statt call_out()-Ketten in einem Raum laufen zu lassen, kann man
        sich auch die letzte Aktivierung merken und bei einem init()
        wieder ein entsprechend langes call_out() starten

     Speicher und das Drumherum:
      Die Speichersituation ist nicht mehr verzweifelt. Das heisst aber
      nicht, dass damit geschlampt werden kann. Gleichzeitig ist die
      Reservierung von Speicher und die Garbage Collection, das Einsammeln
      freigegebenen Speichers bei Freigaben von Variablen (wie bei x+y,
      x=0 (x,y==array/mapping)) immer kostspielig. Folgend ein paar
      Tipps dazu:
      Groesse:
      - wenn moeglich, globale Variablen nach Nutzung freigeben - ggf.
        #defines benutzen: Vorsicht jedoch bei Mapping/Array (siehe unten)
      - globale oder in Properties abgespeicherte Mappings/Arrays/
        Strings klein halten und nur dynamisch erweitern
      - programmiert man an vielen Stellen gleichen Code, dann ist es
        sinnvoll, diesen in eine eigene Datei/Klasse zu giessen und von
        dieser zu erben - das spart Speicher und laesst sich besser warten
      - replace_program bitte nur benutzen, wenn man weiss, was es bewirkt,
        /std/room verwendet es bereits automagisch
 *    - Objekte in Raeumen und NPCs sollten per AddItem() addiert werden,
        da die generelle Aufraeumfunktion /std/room::clean_up() dann weiss
        ob der Raum entfernt werden kann
      - es sollte keine ewigen Objektquellen geben
      - Blueprints:
        - Soll es immer nur ein Objekt von etwas geben, stellt die Blueprint
          per AddItem(...,...,1) dort hin.
          Achtung: Blueprints neu zu laden, ist teuer im Vergleich zum clonen.
                   Gerade bei NPCs (die beim Tod zerstoert werden), sollte
                   man das im Hinterkopf behalten.
        - Die BP von geclonten Objekten muss nicht immer initialisiert werden,
          speziell bei komplexen Objekten kann es sich lohnen, die
          Initialisierung der BP im create abzubrechen. (Denn meistens ist nur
          ihr Programm interessant)
          protected void create() {
            if(!clonep(this_object())) {
              set_next_reset(-1);   // falls die Clones im reset() was
              return;               // machen
            }
            ::create(); ...
          }
      
     Kosten:
 *    - es lohnt, lokale Mappings oder Arrays mit bekannter Groesse via
        allocate() oder m_allocate() vor Belegung in voller benoetiger
        Groesse zu reservieren:
        statt:
          int *x = ({}); foreach(int i: 10) x+=({i});
        lieber:
          int *x = allocate(10); foreach(int i: 10) x[i] = i;
 *    - wiederholtes Ausschneiden (slice) aus Arrays vermeiden, dabei wird
        staendig Speicher neu alloziiert und benutzter Speicher freigegeben:
        statt:
          int *x; ...; while(sizeof(x)) { x[0]...; x=[1..x]; }
        lieber:
          int *x; ...; i=sizeof(x); while(jfun())
      durchgesehen wird. Das hat folgende Konsequenzen:
 *    - jede oeffentliche Methode wird bei call_other() durchsucht und
        das kostet Zeit, wenn eine Methode also nicht oeffentlich sein
        muss, dann schreibt auch ein "protected" davor, wenn sie in den
        erbenden Klassen nicht sichtbar sein muss: "private"
      - nutzt ihr eine fremde Methode mehrfach (zB QueryProp), dann ist es
        an sehr kritischen Stellen sinnvoll, diese einmal zu suchen und an
        eine Lfun-Closure zu binden, weitere Aufrufe sind schneller:
         closure cl;
         cl=symbol_function("QueryProp",this_player());
         funcall(cl, P_LEVEL); funcall(cl, P_SIZE); ...
      Nebenbei bemerkt:
      - es gibt in LPC kein sog. fruehes Binden, "this_object()->function();"
        ist fast immer unnoetig und fast immer nur ein Zeichen fuer Faulheit die
        richtigen Prototypen zu inkludieren/formulieren.

     Lambdas:
      Lambda-Closures sind nicht nur schwer zu lesen, sondern oft auch langsamer
      als andere Closures. Speziell wird bei jedem Auftreten von lambda() die
      Lambda neu erzeugt.
      Nehmt euch die Zeit aus einer Lambda-Closure eine Lfun-Closure zu
      machen oder sie zumindest an eine globale Closure-Variable zu binden,
      damits sie schnell ausgefuehrt werden kann. #define bietet sich hier
      nicht an.
      statt:  filter(users(),
                       lambda(({'x}), ({#'call_other,'x,
                                    "QueryProp",P_SECOND})));
      lieber: private int _isasec(object o) {
                return o->QueryProp(P_SECOND);
              }
              ...
              filter(users(), #'_isasec);
      oder:   closure cl;
              cl=lambda(({'x}), ... );
              ...
              filter(users(), cl);
      oder:
      Bessere Alternative zu Lambdas sind uebrigens inline-closures (man
      inline-closures), die deutlich schneller und einfacher zu lesen sind.
              filter(users(), function mixed (pl)
                              {
                                pl->QueryProp(P_SECOND);
                              }
                    );


     Simul-efun und die Last der Vergangenheit:
      Es gibt einige Simul-Efuns, die anstelle einer aehnlichen Efun verwendet
      werden, aber langsamer sind. Beispiel: die sefun m_copy_delete() macht
      fast das gleiche wie m_delete(), erzeugt aber vorher immer eine Kopie.
      Wenn man diese nicht braucht, sollte man m_delete() den Vorzug geben.


     Generelle Bemerkungen:
 ***  - LAG entsteht vor allem dann, wenn zu viele Dinge auf einmal
        identifiziert, bewegt, geladen, gecloned oder kopiert werden
        sollen (in nur einem Kommando, in einem reset(), ...)
        - zerlegt solche Aufgaben mit call_out/heart_beat in Haeppchen
        - lasst es einen Erzmagier durchsehen
 *    - Variablen sind immer auf 0 initialisiert,
        allocate()-Arrays sind mit 0 oder Wunschwert initialisiert.
      - gleicher Code sollte aus Schleifen sollten entfernt werden,
        zB bei Iteration ueber ein Array gehoert das sizeof() vor die
        Schleife, nicht in den Test
 *    - beim Identifizieren eindeutiger Objekte ist present_clone()
        wesentlich billiger als ein present() + geschuetzten IDs
 *    - aus Arrays koennen mittels "-" viele identische Werte auf einmal
        entfernt werden, es ist also sinnvoll bei Loeschoperationen
        zu loeschende Werte auf einen bestimmten Wert zu setzen und diesen
        dann mittels array-=({wert}) zu entfernen.
        Wir entfernen alle getoeteten NPC, d.h. alle geloeschten Objekte
        aus einer Liste: meinelistemitnpcs-=({0})
      - efuns sind oft schneller als eigene Konstrukte, gerade was
        Arrays betrifft. Pauschalisiert kann das nicht werden, man muss
        auch immer die noetige Reservierung von Speicher mitbetrachten!
        Zusammen mit einer Referenz sind sort_array(), filter(), map() etc.
        dennoch oft euer Freund:
        statt:   t=allocate(0);
                 for (i=sizeof(a1); i--; )
                   if (member(a2,a1[i])>=0) t+=({a1[i]});
        lieber:  private mixed _is_member(mixed x, a) {
                   if (member(a,x)>=0) return 1;
                   else return 0;
                 }
                 ...
                 t=filter(a1, #'_is_member, &a2);
        oder hier noch besser:
                   t=a1&a2;
      - x&y ist bei zwei grossen Arrays manchmal die schlechtere Wahl:
        statt:   t=all_inventory(TO)&users();       // zwei Arrays
        lieber:  t=filter(all_inventory(TO),        // ein Array!
                          #'query_once_interactive);
        Eventuell lohnt es sich hier, gleich mit first_inventory() und
        next_inventory() ueber den Raum zu iterieren und auf allen
        query_once_interactive() die gewuenschten Operationen vorzunehmen.
      - foreach() ist oft gegenueber for() die bessere Alternative (etwas
        schneller, einfacher formuliert)
      - weitere schnelle efuns:
        query_verb(), interactive(), query_once_interactive(), living(),
        stringp(), intp(), closurep(), objectp(), ...

 SIEHE AUCH:
     memory, objekte, mudrechner, goodstyle, ticks


Letzte Aenderung: 22.12.2016, Bugfix


zurück zur Übersicht

YOUTUBE | FACEBOOK | TWITTER | DISCORD | FEEDBACK | IMPRESSUM | DATENSCHUTZ 1992–2023 © MorgenGrauen.