MG Mud User | 88f1247 | 2016-06-24 23:31:02 +0200 | [diff] [blame^] | 1 | Effizienz |
| 2 | BESCHREIBUNG: |
| 3 | Effizienz in der Programmierung ist leider nicht ganz so einfach zu |
| 4 | beschreiben, da es viel mit der zugrundeliegenden Verarbeitung der |
| 5 | Programme zu tun hat. Es geht ganz gut am Beispiel. |
| 6 | |
| 7 | Generell haben Lesbarkeit und Wartbarkeit von Code Vorrang vor dessen |
| 8 | Effizienz, gerade weil die wirklich arbeitslastigen Methoden in der Lib |
| 9 | stecken. Ausserdem ist es im Allgemeinen nicht empfehlenswert, (viel) |
| 10 | Aufwand in die Optimierung von Code zu stecken, solange nicht klar ist, |
| 11 | dass dies ueberhaupt notwendig ist. |
| 12 | Les-/Wartbarkeit und effizienter Stil schliessen sich aber nicht aus und |
| 13 | einige (einfache) Grundregeln lassen sich einfach einhalten. |
| 14 | |
| 15 | Fuer diejenigen unter euch, die gerade erst mit LPC zusammenstossen |
| 16 | gibt es (*) an den besonders wichtigen Stellen. Auf Dauer solltet ihr |
| 17 | aber mal alle Eintraege ueberfliegen. Den ersten koennen alle hier |
| 18 | beherzigen: |
| 19 | |
| 20 | LPC wird beim Laden nicht optimiert: |
| 21 | Das was ihr schreibt, wird auch so ausgefuehrt, es werden keine |
| 22 | Schleifen optimiert, keine unnoetigen Zuweisungen entfernt, nichts |
| 23 | wird veraendert: |
| 24 | - ueberlegt euch also euren Code gut, wenn er an kritischen Stellen |
| 25 | steht oder sehr viel Rechenzeit kostet (zB geschachtelte Schleifen) |
| 26 | - testet einfach mal Varianten und fragt auf -lpc nach Optimierung! |
| 27 | |
| 28 | call_out und heart_beat erzeugen konstante Last: |
| 29 | Jeder call_out() steht in einer Liste, die im selben Takt wie der |
| 30 | heart_beat() durchsucht wird. Beides kostet Zeit. Beide Methoden |
| 31 | verhindern zudem das Ausswappen des entsprechenden Objektes. Deshalb |
| 32 | schalten sich Raummeldungen (AddRoomMessage funktioniert ueber |
| 33 | call_out()) und der heart_beat() von /std/npc nach dem Verlassen des |
| 34 | Raumes durch den letzten Spieler selbst aus. |
| 35 | * - bitte achtet darauf, unnoetige call_out/heart_beat zu vermeiden. |
| 36 | (Insbesondere sich bewegende NPCs sollten sich auch irgendwann |
| 37 | wieder abschalten - es gibt einen funktionierenden MNPC mit diesen |
| 38 | Eigenschaften unter /p/service/padreic/mnpc.) |
| 39 | - fuer regelmaessige Aufrufe in einem Objekt, wo der genaue Zeitpunkt |
| 40 | nicht auf einige Sekunden ankommt, bietet sich auch reset() mit |
| 41 | set_next_reset() an |
| 42 | - statt call_out()-Ketten in einem Raum laufen zu lassen, kann man |
| 43 | sich auch die letzte Aktivierung merken und bei einem init() |
| 44 | wieder ein entsprechend langes call_out() starten |
| 45 | |
| 46 | Speicher und das Drumherum: |
| 47 | Die Speichersituation ist nicht mehr verzweifelt. Das heisst aber |
| 48 | nicht, dass damit geschlampt werden kann. Gleichzeitig ist die |
| 49 | Reservierung von Speicher und die Garbage Collection, das Einsammeln |
| 50 | freigegebenen Speichers bei Freigaben von Variablen (wie bei x+y, |
| 51 | x=0 (x,y==array/mapping)) immer kostspielig. Folgend ein paar |
| 52 | Tipps dazu: |
| 53 | Groesse: |
| 54 | - wenn moeglich, globale Variablen nach Nutzung freigeben - ggf. |
| 55 | #defines benutzen: Vorsicht jedoch bei Mapping/Array (siehe unten) |
| 56 | - globale oder in Properties abgespeicherte Mappings/Arrays/ |
| 57 | Strings klein halten und nur dynamisch erweitern |
| 58 | - programmiert man an vielen Stellen gleichen Code, dann ist es |
| 59 | sinnvoll, diesen in eine eigene Datei/Klasse zu giessen und von |
| 60 | dieser zu erben - das spart Speicher und laesst sich besser warten |
| 61 | - replace_program bitte nur benutzen, wenn man weiss, was es bewirkt, |
| 62 | /std/room verwendet es bereits automagisch |
| 63 | * - Objekte in Raeumen und NPCs sollten per AddItem() addiert werden, |
| 64 | da die generelle Aufraeumfunktion /std/room::clean_up() dann weiss |
| 65 | ob der Raum entfernt werden kann |
| 66 | - es sollte keine ewigen Objektquellen geben |
| 67 | - Blueprints: |
| 68 | - Soll es immer nur ein Objekt von etwas geben, stellt die Blueprint |
| 69 | per AddItem(...,...,1) dort hin. |
| 70 | Achtung: Blueprints neu zu laden, ist teuer im Vergleich zum clonen. |
| 71 | Gerade bei NPCs (die beim Tod zerstoert werden), sollte |
| 72 | man das im Hinterkopf behalten. |
| 73 | - Die BP von geclonten Objekten muss nicht immer initialisiert werden, |
| 74 | speziell bei komplexen Objekten kann es sich lohnen, die |
| 75 | Initialisierung der BP im create abzubrechen. (Denn meistens ist nur |
| 76 | ihr Programm interessant) |
| 77 | protected void create() { |
| 78 | if(!clonep(this_object())) { |
| 79 | set_next_reset(-1); // falls die Clones im reset() was |
| 80 | return; // machen |
| 81 | } |
| 82 | ::create(); ... |
| 83 | } |
| 84 | |
| 85 | Kosten: |
| 86 | * - es lohnt, lokale Mappings oder Arrays mit bekannter Groesse via |
| 87 | allocate() oder m_allocate() vor Belegung in voller benoetiger |
| 88 | Groesse zu reservieren: |
| 89 | statt: |
| 90 | int *x = ({}); foreach(int i: 10) x+=({i}); |
| 91 | lieber: |
| 92 | int *x = allocate(10); foreach(int i: 10) x[i] = i; |
| 93 | * - wiederholtes Ausschneiden (slice) aus Arrays vermeiden, dabei wird |
| 94 | staendig Speicher neu alloziiert und benutzter Speicher freigegeben: |
| 95 | statt: |
| 96 | int *x; ...; while(sizeof(x)) { x[0]...; x=[1..x]; } |
| 97 | lieber: |
| 98 | int *x; ...; i=sizeof(x); while(j<i) { x[j]...; j++; } |
| 99 | * - direkte Mapping/Array ({}), ([]) in Methoden (zB ueber #define) |
| 100 | sparen zwar globalen Platz, kosten aber Konstruktionszeit bei jedem |
| 101 | Aufruf dieser Methoden - fuer haeufig gerufene Methoden sollten |
| 102 | grosse Datenstrukturen einmal global konstruiert werden |
| 103 | statt: #define GROSSES_MAPPING ([....]) |
| 104 | void haeufige_fun() { ... GROSSES_MAPPING ... } |
| 105 | lieber: mapping GROSSES_MAPPING = ([....]); |
| 106 | void haeufige_fun() { ... GROSSES_MAPPING ... } |
| 107 | * - diverse efuns sind genauso schnell zugreifbar wie Variablen, |
| 108 | muessen also nur zugewiesen werden, wenn sich der Wert aendern kann: |
| 109 | this_player(), this_interactive(), environment(), previous_object(), |
| 110 | this_object(). |
| 111 | * - statt all_inventory() einer Variablen zuzuweisen und darueber |
| 112 | zu iterieren, kann man oft mit first_inventory() und next_inventory() |
| 113 | ein Inventory durchgehen |
| 114 | |
| 115 | Methoden: |
| 116 | Die Methoden eines Objektes werden in einer Liste gespeichert, die |
| 117 | beim Aufruf einer Methode ueber call_other() (oder o->fun()) |
| 118 | durchgesehen wird. Das hat folgende Konsequenzen: |
| 119 | * - jede oeffentliche Methode wird bei call_other() durchsucht und |
| 120 | das kostet Zeit, wenn eine Methode also nicht oeffentlich sein |
| 121 | muss, dann schreibt auch ein "protected" davor, wenn sie in den |
| 122 | erbenden Klassen nicht sichtbar sein muss: "private" |
| 123 | - nutzt ihr eine fremde Methode mehrfach (zB QueryProp), dann ist es |
| 124 | an sehr kritischen Stellen sinnvoll, diese einmal zu suchen und an |
| 125 | eine Lfun-Closure zu binden, weitere Aufrufe sind schneller: |
| 126 | closure cl; |
| 127 | cl=symbol_function("QueryProp",this_player()); |
| 128 | funcall(cl, P_LEVEL); funcall(cl, P_SIZE); ... |
| 129 | Nebenbei bemerkt: |
| 130 | - es gibt in LPC kein sog. fruehes Binden, "this_object()->function();" |
| 131 | ist fast immer unnoetig und fast immer nur ein Zeichen fuer Faulheit die |
| 132 | richtigen Prototypen zu inkludieren/formulieren. |
| 133 | |
| 134 | Lambdas: |
| 135 | Lambda-Closures sind nicht nur schwer zu lesen, sondern oft auch langsamer |
| 136 | als andere Closures. Speziell wird bei jedem Auftreten von lambda() die |
| 137 | Lambda neu erzeugt. |
| 138 | Nehmt euch die Zeit aus einer Lambda-Closure eine Lfun-Closure zu |
| 139 | machen oder sie zumindest an eine globale Closure-Variable zu binden, |
| 140 | damits sie schnell ausgefuehrt werden kann. #define bietet sich hier |
| 141 | nicht an. |
| 142 | statt: filter(users(), |
| 143 | lambda(({'x}), ({#'call_other,'x, |
| 144 | "QueryProp",P_SECOND}))); |
| 145 | lieber: private static int _isasec(object o) { |
| 146 | return o->QueryProp(P_SECOND); |
| 147 | } |
| 148 | ... |
| 149 | filter(users(), #'_isasec); |
| 150 | oder: closure cl; |
| 151 | cl=lambda(({'x}), ... ); |
| 152 | ... |
| 153 | filter(users(), cl); |
| 154 | oder: |
| 155 | Bessere Alternative zu Lambdas sind uebrigens inline-closures (man |
| 156 | inline-closures), die deutlich schneller und einfacher zu lesen sind. |
| 157 | filter(users(), function mixed (pl) |
| 158 | { |
| 159 | pl->QueryProp(P_SECOND); |
| 160 | } |
| 161 | ); |
| 162 | |
| 163 | |
| 164 | Simul-efun und die Last der Vergangenheit: |
| 165 | Es gibt einige Simul-Efuns, die anstelle einer aehnlichen Efun verwendet |
| 166 | werden, aber langsamer sind. Beispiel: die sefun m_copy_delete() macht |
| 167 | fast das gleiche wie m_delete(), erzeugt aber vorher immer eine Kopie. |
| 168 | Wenn man diese nicht braucht, sollte man m_delete() den Vorzug geben. |
| 169 | |
| 170 | |
| 171 | Generelle Bemerkungen: |
| 172 | *** - LAG entsteht vor allem dann, wenn zu viele Dinge auf einmal |
| 173 | identifiziert, bewegt, geladen, gecloned oder kopiert werden |
| 174 | sollen (in nur einem Kommando, in einem reset(), ...) |
| 175 | - zerlegt solche Aufgaben mit call_out/heart_beat in Haeppchen |
| 176 | - lasst es einen Erzmagier durchsehen |
| 177 | * - Variablen sind immer auf 0 initialisiert, |
| 178 | allocate()-Arrays sind mit 0 oder Wunschwert initialisiert. |
| 179 | - gleicher Code sollte aus Schleifen sollten entfernt werden, |
| 180 | zB bei Iteration ueber ein Array gehoert das sizeof() vor die |
| 181 | Schleife, nicht in den Test |
| 182 | * - beim Identifizieren eindeutiger Objekte ist present_clone() |
| 183 | wesentlich billiger als ein present() + geschuetzten IDs |
| 184 | * - aus Arrays koennen mittels "-" viele identische Werte auf einmal |
| 185 | entfernt werden, es ist also sinnvoll bei Loeschoperationen |
| 186 | zu loeschende Werte auf einen bestimmten Wert zu setzen und diesen |
| 187 | dann mittels array-=({wert}) zu entfernen. |
| 188 | Wir entfernen alle getoeteten NPC, d.h. alle geloeschten Objekte |
| 189 | aus einer Liste: meinelistemitnpcs-=({0}) |
| 190 | - efuns sind oft schneller als eigene Konstrukte, gerade was |
| 191 | Arrays betrifft. Pauschalisiert kann das nicht werden, man muss |
| 192 | auch immer die noetige Reservierung von Speicher mitbetrachten! |
| 193 | Zusammen mit einer Referenz sind sort_array(), filter(), map() etc. |
| 194 | dennoch oft euer Freund: |
| 195 | statt: t=allocate(0); |
| 196 | for (i=sizeof(a1); i--; ) |
| 197 | if (member(a2,a1[i])>=0) t+=({a1[i]}); |
| 198 | lieber: private static mixed _is_member(mixed x, a) { |
| 199 | if (member(a,x)>=0) return 1; |
| 200 | else return 0; |
| 201 | } |
| 202 | ... |
| 203 | t=filter(a1, #'_is_member, &a2); |
| 204 | oder hier noch besser: |
| 205 | t=a1&a2; |
| 206 | - x&y ist bei zwei grossen Arrays manchmal die schlechtere Wahl: |
| 207 | statt: t=all_inventory(TO)&users(); // zwei Arrays |
| 208 | lieber: t=filter(all_inventory(TO), // ein Array! |
| 209 | #'query_once_interactive); |
| 210 | Eventuell lohnt es sich hier, gleich mit first_inventory() und |
| 211 | next_inventory() ueber den Raum zu iterieren und auf allen |
| 212 | query_once_interactive() die gewuenschten Operationen vorzunehmen. |
| 213 | - foreach() ist oft gegenueber for() die bessere Alternative (etwas |
| 214 | schneller, einfacher formuliert) |
| 215 | - weitere schnelle efuns: |
| 216 | query_verb(), interactive(), query_once_interactive(), living(), |
| 217 | stringp(), intp(), closurep(), objectp(), ... |
| 218 | |
| 219 | SIEHE AUCH: |
| 220 | memory, objekte, mudrechner, goodstyle, ticks |
| 221 | |
| 222 | 6. Sep 2012 Gloinson |