blob: 3f99c48f4b0f9e08d6ea5c88e3c151602c957824 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001Effizienz
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