Ich will die Laufzeit und den Speicherverbrauch der Funktion einer
Klasse optimieren.
Im folgenden Beispiel gibt es die Funktion "exec" mit einem Eingang und
einem Ausgang. Wird die Ausführungszeit kleiner wenn man für den Eingang
den direkten Wert statt des Pointers nimmt?
Der Compiler ist gut. Er scheint die Klasse vollständig wegzuoptimieren
und hält die Variablen in den Registern. Also kann man vermutlich nichts
optimieren, wenn man den Input statt als Zeiger als Pointer übergibt.
Christoph M. schrieb:> Wird die Ausführungszeit kleiner wenn man für den Eingang> den direkten Wert statt des Pointers nimmt?
Grundsätzlich ja, aber nur minimal.
Christoph M. schrieb:> Der Compiler ist gut. Er scheint die Klasse vollständig wegzuoptimieren
"Klassen" kann man sowieso nicht wegoptimieren, weil "Klassen" nicht im
Assemblercode landen; Klassen sind ein abstraktes Konzept, im
Assemblercode landen nur Funktionen. In deinem Fall wurde die Funktion
inlined und somit der Pointer wegoptimiert; das passiert nicht immer,
z.B. wenn die Funktion größer ist und mehrfach aufgerufen wird oder
rekursiv ist.
Allerdings: (Kleine) Input-Daten per Pointer übergeben ist relativ
sinnlos, für den Leser verwirrend und sollte schon eher vermieden
werden. Mindestens mal sollte ein "const" dran. Kleine Output-Daten
sollte man auch eher per Rückgabewert zurückgeben, nicht per Pointer.
Christoph M. schrieb:> Ich brauche Klassen mit mehreren Ausgängen:
Dann kann man sich überlegen, ob man einen Ausgang als Rückgabewert
nutzen möchte, weil es der "primäre" Ausgang ist o.ä.
Außerdem kannst du Referenzen nutzen:
1
voidexec(constint&input,int&outputA,int&outputB)
2
{
3
outputA=input+1;
4
outputB=input+2;
5
}
6
7
X1.exec(Var1,Var2,Var3);
8
X2.exec(Var2,Var3,Var4);
9
X3.exec(Var3,Var4,Var1);
Die sind sehr ähnlich zu Pointern, signalisieren (dem Leser) aber
eindeutig dass es nur ein einzelner Wert (und kein Array) ist, dass sich
die Adresse nicht ändert, und dass die Adresse gültig ist (und nicht
NULL/nullptr sein kann wie bei Pointern).
>Außerdem kannst du Referenzen nutzen:
Die Referenzen sehen auf jeden Fall nicht schlecht aus. Kann es sein,
dass es die nicht schon immer in C++ gibt? Die Frage wäre, ob sie auf
jedem System kompiliert werden.
Christoph M. schrieb:> Der Compiler ist gut. Er scheint die Klasse vollständig wegzuoptimieren> und hält die Variablen in den Registern.
Das bei solch vereinfachten Beispielen eigentlich immer der Fall.
Oliver
Niklas G. schrieb:> Optimierung auf Lesbarkeit keineswegs.
Von Lesbarkeit war keine Rede:
Christoph M. schrieb:> Ich will die Laufzeit und den Speicherverbrauch der Funktion einer> Klasse optimieren.
Harald K. schrieb:> Von Lesbarkeit war keine Rede:
Bei mir schon:
Niklas G. schrieb:> für den Leser verwirrend
Durchaus ein Punkt den man optimieren kann.
Wenn mehrere Elemente zusammen gehören, faßt man sie gerne als Struct
zusammen. Das verbessert einmal die Lesbarkeit und man muß nur einen
Pointer auf die Struct übergeben, anstelle haufenweise einzelne Pointer
oder einzelne Variablen.
Christoph M. schrieb:> X1.exec(&Var1, &Var2,&Var3);> X2.exec(&Var2, &Var3,&Var4);> X3.exec(&Var3, &Var4,&Var1);
Das sieht dann allerdings eher nach einem Array aus (mit Wraparound).
Auch für die völlig obskuren JVM-C++ Compiler werden die o.g.
gewöhnlichen Optimierungen helfen. Dein total abstrakter Artikel
hingegen nicht.
Also, troll woanders.
Niklas G. schrieb:> Also, troll woanders.
Der Link ist einfach ein Hinweis, dass die Ausgangsfrage ziemlich
getrollt ist.
Deswegen war der auch nur als guter Rat gemeint. Wird doch wohl erlaubt
sein?
Klasse ist einfach nicht der passende Begriff, um ihn auf Lowlevel-Ebene
zu untersuchen.
Zur Auseinandersetzung mit Klassen ist bei Java ein, wie ich finde,
besserer Backround da.
Rbx schrieb:> Der Link ist einfach ein Hinweis, dass die Ausgangsfrage ziemlich> getrollt ist.
Die Ausgangsfrage ist offensichtlich einfach nur eine Anfängerfrage.
Rbx schrieb:> Deswegen war der auch nur als guter Rat gemeint
Dann konntest du den guten Rat nicht weniger von-oben-herab verpacken?
Insbesondere auch weil
Rbx schrieb:> Klasse ist einfach nicht der passende Begriff, um ihn auf Lowlevel-Ebene> zu untersuchen.
bereits erwähnt wurde:
Niklas G. schrieb:> "Klassen" kann man sowieso nicht wegoptimieren, weil "Klassen" nicht im> Assemblercode landen
> "Klassen" kann man sowieso nicht wegoptimieren, weil "Klassen" nicht im> Assemblercode landen
Man kann Klassen allerdings sehr unterschiedlich implementieren. Sie
brauchen dann mehr oder weniger Speicher und sind mehr oder weniger
effizient. Bei zeitkritischen Systemen gibt es da große Unterschiede.
Niklas G. schrieb:> Harald K. schrieb:>> Von Lesbarkeit war keine Rede:>> Bei mir schon:
Es ging wohl um den TO, dessen Beispiele weder lesbarer noch
performanter sind als ein naiver, direkter Ansatz. (Dass wir seine
eigentliche Aufgabe nicht kennen, geschenkt)
Niklas G. schrieb:> Christoph M. schrieb:>> Der Compiler ist gut. Er scheint die Klasse vollständig wegzuoptimieren>> "Klassen" kann man sowieso nicht wegoptimieren, weil "Klassen" nicht im> Assemblercode landen;
Anführungszeichen braucht man nicht.
> Klassen sind ein abstraktes Konzept, im Assemblercode landen nur> Funktionen.
Eigentlich gibt es auch "Funktionen" in dem Sinne nicht. Sowas wie
Funktionsparameter oder einen Returnwert kennt Assembler nicht,
geschweige denn Exceptions und diverse andere Konzepte, die C++ bei
Funktionen kennt. Es gibt nur den Sprung zu einem Ziel und den
Rücksprung, der automatisch zur ursprünglichen Stelle springt.
> In deinem Fall wurde die Funktion inlined und somit der Pointer> wegoptimiert; das passiert nicht immer, z.B. wenn die Funktion größer ist> und mehrfach aufgerufen wird oder rekursiv ist.
Oder wenn der Compiler den Inhalt der Funktion an der Stelle nicht
kennt.
> Allerdings: (Kleine) Input-Daten per Pointer übergeben ist relativ> sinnlos, für den Leser verwirrend und sollte schon eher vermieden> werden. Mindestens mal sollte ein "const" dran.
Eigentlich sehe ich bei solchen Daten nur gerade dann einen Sinn in
einem Zeiger, wenn sie nicht const sind, so dass die Funktion da was
reinschreiben kann.
> Kleine Output-Daten sollte man auch eher per Rückgabewert zurückgeben,> nicht per Pointer.
Die Pointer-Variante wird gerne verwendet, wenn mehrere Sachen auf
einmal zurückgegeben werden müssen. Allerdings geht das in modernem C++
auch leicht über den Returnwert, ohne dass man dazu extra gleich einen
eigenen Typ definieren muss:
Rolf M. schrieb:> Es gibt nur den Sprung zu einem Ziel und den> Rücksprung, der automatisch zur ursprünglichen Stelle springt.
Naja, nicht ganz. Man hat auch Übergabeparameter,
Registersicherungen,-Wiederhervorholungen, oder auch Rückgabeparameter
drin, wenn man möchte.
Gruppengeschichten kann man über Moduleinrichtung organisieren,
Auto-Geschichten u.a. über die beim Intel (Index-Register). Beim mc muss
man sich sowas eher selber einrichten. Je nachdem, was die Hardware so
im Angebot hat.
Außerdem da auch Möglichkeiten, zu entscheiden, ob man die Rechnerei
mehr über die Register laufen lassen möchte, oder mehr lieber mit viel
Speicherzugriffen, die aber üblicherweise Zeit kosten.
Müsste man sich aber im Einzelfall genauer ansehen. Mit
Generalisierungen kann man hier kaum einen Blumentopf gewinnen.
Rolf M. (rmagnus)
>Eigentlich sehe ich bei solchen Daten nur gerade dann einen Sinn in>einem Zeiger, wenn sie nicht const sind, so dass die Funktion da was>reinschreiben kann.
1
voidexec(constint&input,int&outputA,int&outputB)
Ich hoffe, das "const" bezieht sich auf den Zeiger und nicht auf den
Inhalt, auf den er zeigt.
Christoph M. schrieb:> Ich hoffe, das "const" bezieht sich auf den Zeiger und nicht auf den> Inhalt, auf den er zeigt.
Das sind Referenzen und nicht Zeiger, und die Referenz selber ist immer
"konstant", also man kann die Adresse nicht ändern (anders als beim
Pointer). Somit ist "const int&" immer eine (automatisch konstante)
Referenz auf einen konstanten Integer.
- "const int*" - Nicht-konstanter Zeiger auf konstantes int
- "int* const" - Konstanter Zeiger auf nicht-konstantes int
- "const int* const" - Konstanter Zeiger auf konstantes int
- "int&" - (Automatisch konstante) Referenz auf nicht-konstantes int
- "const int&" - (Automatisch konstante) Referenz auf konstantes int
Christoph M. schrieb:> Die Referenzen sehen auf jeden Fall nicht schlecht aus. Kann es sein,> dass es die nicht schon immer in C++ gibt? Die Frage wäre, ob sie auf> jedem System kompiliert werden.
Wie lange programmierst du schon in C++?
Egal, wie lange, Referenzen gibt es länger…
Oliver
Reflexartig Referenzen zu verwenden is m.E. keine gute Idee.
Besser:
1
voidexec(intinput,int&outputA,int&outputB)
2
{
3
outputA=input+1;
4
outputB=input+2;
5
}
Erklärung:
"input" belegt nur 2 Bytes und wird von exec nicht geändert, man kann es
also ebenso gut per Value übergeben.
Wenn exec geinlined wird, vertut man sich nix und der erzeugte Code ist
vermutlich der gleiche (modulo Compiler-Quirx), der C++ Code liest sich
aber leichter.
Wenn exec jedoch nicht geinlined wird, ist const int &input teuer, weil
(effektiv) die Adresse einer Variablen übergeben wird, und um die
Adresse eines auto-Objekts nehmen zu können, muss es im Speicher liegen;
hier also im Frame der Funktion. Und das ist wirklich teuer: Zum einen
muss ein Frame angelegt werden, das Objekt darin gespeicher werden (auch
Konstanten!), zum anderen muss die Adresse berechnet werden. Und der
Callee muss derefernzieren, was ebenfalls teuer ist: Adress-Register und
RAM-Zugriff werden erforderlich.
Selbst wenn input ein 32-Bit Wert ist, dürfte es effizienter sein, per
Value zu übergeben statt per Referenz.
Für output-Operanden gilt evtl. ähnliches: ein int besser als
Rückgabewert statt als Referenz. Oder gar ein struct mit 2 ints (kann
avr-gcc in Registern halten).
Johann L. schrieb:> Besser:> 1void exec (int input, int &outputA, int &outputB)> 2{> 3 outputA = input + 1;> 4 outputB = input + 2;> 5}
Da der Compiler die Funktion eh komplett wegoptimiert, ist’s eigentlich
völlig egal…
Oliver
Rbx schrieb:> Rolf M. schrieb:>> Es gibt nur den Sprung zu einem Ziel und den>> Rücksprung, der automatisch zur ursprünglichen Stelle springt.>> Naja, nicht ganz. Man hat auch Übergabeparameter,> Registersicherungen,-Wiederhervorholungen, oder auch Rückgabeparameter> drin, wenn man möchte.
Bei welcher Architektur? In der Regel gibt es sowas auf Assenbler-Ebene
nicht. Man muss das selber z.B. auf Basis von Registern oder dem Stack
nachbilden.
> Gruppengeschichten kann man über Moduleinrichtung organisieren,> Auto-Geschichten u.a. über die beim Intel (Index-Register).
Ich weiß nicht genau, was du damit meinst.
> Außerdem da auch Möglichkeiten, zu entscheiden, ob man die Rechnerei> mehr über die Register laufen lassen möchte, oder mehr lieber mit viel> Speicherzugriffen, die aber üblicherweise Zeit kosten.
In der Regel will man sie so viel wie möglich über Register machen, eben
weil die Speicherzugriffe zusätzliche Zeit kosten und sonst eigentlich
keinen Vorteil haben.