Forum: Compiler & IDEs Klasse Optimierung Laufzeit und Speicher CPP


von Christoph M. (mchris)


Lesenswert?

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?
1
int Var1 = 0;
2
int Var2 = 0;
3
4
class Test
5
{
6
  public:
7
8
    Test(int a)
9
    {
10
      value = a;
11
    }
12
13
    void exec(int *input, int *output)
14
    {
15
      *output = *input + 1;
16
    }
17
18
  private:
19
    int value;
20
21
};
22
23
Test myObject(0);
24
25
void setup()
26
{
27
  Serial.begin(115200);
28
}
29
30
void loop()
31
{
32
  myObject.exec(&Var1, &Var2);
33
  Serial.println(Var2);
34
  Var1 = Var2;
35
  delay(500);
36
}

von Christoph M. (mchris)


Angehängte Dateien:

Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Christoph M. (mchris)


Lesenswert?

>Mindestens mal sollte ein "const" dran.

Du meinst so?:
1
    void exec(const int *input, int *output)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Christoph M. schrieb:
> Du meinst so?:

Ja! Aber warum nicht:
1
int exec(int input)
2
{
3
  return input + 1;
4
}

: Bearbeitet durch User
von Christoph M. (mchris)


Lesenswert?

Ich brauche Klassen mit mehreren Ausgängen:
1
int Var1 = 0;
2
int Var2 = 0;
3
int Var3 = 0;
4
int Var4 = 0;
5
6
class Test
7
{
8
  public:
9
10
    Test(int a)
11
    {
12
      value = a;
13
    }
14
15
    void exec(const int *input, int *outputA, int *outputB)
16
    {
17
      *outputA = *input + 1;
18
      *outputB = *input + 2;
19
    }
20
21
  private:
22
    int value;
23
24
};
25
26
Test X1(0);
27
Test X2(0);
28
Test X3(0);
29
30
void setup()
31
{
32
  Serial.begin(115200);
33
}
34
35
#define Ende 10000000L
36
37
void loop()
38
{
39
  for(uint32_t n=0;n<Ende;n++)
40
  {
41
    X1.exec(&Var1, &Var2,&Var3);
42
    X2.exec(&Var2, &Var3,&Var4);
43
    X3.exec(&Var3, &Var4,&Var1);
44
  }
45
  Serial.println(Var2);
46
}

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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
void exec(const int &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).

von Christoph M. (mchris)


Lesenswert?

>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.

von Harald K. (kirnbichler)


Lesenswert?

Premature optimization is the root of all evil.

https://xkcd.com/1691/

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Harald K. schrieb:
> Premature optimization is the root of all evil.

Optimierung auf Lesbarkeit keineswegs. Und das ist hier durchaus 
relevant.

von Oliver S. (oliverso)


Lesenswert?

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

von Harald K. (kirnbichler)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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).

von Rbx (rcx)


Lesenswert?

Christoph M. schrieb:
> Ich will die Laufzeit und den Speicherverbrauch der Funktion einer
> Klasse optimieren.

Fang erstmal hier an:
https://www.computerweekly.com/de/tipp/Tipps-zum-Java-Performance-Tuning-um-die-JVM-zu-optimieren

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?


von Rbx (rcx)


Lesenswert?

Niklas G. schrieb:
> Wie genau hilft die JVM bei C++?

Welchen C++ Compiler (bezüglich der Frage) meinst du jetzt genau?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch User
von Rbx (rcx)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von Christoph M. (mchris)


Lesenswert?

> "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.

von Bruno V. (bruno_v)


Lesenswert?

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)

von Rolf M. (rmagnus)


Lesenswert?

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:
1
std::tuple<double, double> Point::coordinates() const
2
{
3
    return { x, y };
4
}
5

6
    auto [ x, y ] = my_point.coordinates();

von Rbx (rcx)


Lesenswert?

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.

von Christoph M. (mchris)


Lesenswert?

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
void exec(const int &input, int &outputA, int &outputB)
Ich hoffe, das "const" bezieht sich auf den Zeiger und nicht auf den 
Inhalt, auf den er zeigt.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von Oliver S. (oliverso)


Lesenswert?

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

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Niklas G. schrieb:
1
> void exec(const int &input, int &outputA, int &outputB)
2
> {
3
>   outputA = input + 1;
4
>   outputB = input + 2;
5
> }

Reflexartig Referenzen zu verwenden is m.E. keine gute Idee.

Besser:
1
void exec (int input, 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).

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Reflexartig Referenzen zu verwenden is m.E. keine gute Idee.

Hab ich doch schon ganz zu Anfang erwähnt...

von Oliver S. (oliverso)


Lesenswert?

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

von Rolf M. (rmagnus)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.