wenn sind lokale oder globale Variablen besser? Optimierungen?

ich bin gerade auf dem Optimierungstrip. :slight_smile:

Damit verbringe ich auch relativ viel Zeit.

Udo Klein hat mir mal dieses Buch empohlen, welches ich hiermit wärmstens weiterempfehlen möchte.

Dort steht sinngemäß drin, wenn man Software beschleunigen möchte, stellt man sich am Besten die Aufgabe, den Ablauf um den Faktor 3 (!) zu beschleunigen.

Wenn man Ideen hat, wie man dieses ehrgeizige Ziel umsetzen könnte, landet man meistens bei einem völlig neuen Design. Und wenn man dabei nur auf eine Verdopplung der Geschwindigkeit kommt, ist das ja auch schon beachtlich.

Feintuning und Mikrooptimierung, die ein paar Promille mehr Speed mit dem neuen Design bringt, kommt danach (wenn überhaupt).

Sich oft durchlaufene Schleifen sehr genau anzusehen, ist auf jeden Fall immer sinnvoll.

Ich optimiere IMMER auf Geschwindigkeit und wenn der RAM nicht reicht, brauche ich eben eine dickere Plattform. Aber das muss jeder für sich und sein Projekt entscheiden, was Priorität hat.

Beste Grüße,

Helmuth

Hallo,

schön, interessant, lehrreich :slight_smile:

also halte ich erstmal fest.

a) lokale Variablen sind immer einen Tick langsamer wie globale, wobei das vernachlässigbar ist
b) lokale Variablen belegen keinen RAM, weil die dafür genutzten Register außerhalb liegen
c) globale Variablen liegen im RAM
d) man kann nicht unendlich viele lokale Variablen verwenden, sonst sprengt es den Stack (eher theoretisch)

Am Ende lieber lokale Variablen verwenden statt zu viele globale. Zudem der Code lesbarer bleibt.

Soweit richtig verstanden?

Sind die Register von denen die Rede ist die gleichen die man in Assembler mit r16, r17 usw. verwendet? Diese 32 purpose Register.


@Helmuth
Die Frage ist eher zufällig entstanden, als ich meine Funktionen mit globalen und lokalen Variablen angeschaut habe und dann noch mit call by Reference hantiert hatte. Dadurch kam die Frage, macht das eine oder andere überhaupt so richtig Sinn.

So absoluten Speed wie du ihn für deine LED Effekte benötigst, werde ich nicht benötigen. Da würde ich dann eher zu einem schnellerem µC greifen. :slight_smile:

Die Frage ist eher zufällig entstanden, als ich meine Funktionen mit globalen und lokalen Variablen angeschaut habe und dann noch mit call by Reference hantiert hatte. Dadurch kam die Frage, macht das eine oder andere überhaupt so richtig Sinn.

Die Frage ist interessant und ich habe in diesem Thread auch Neues gelernt.

Mich würden konkret gemessene Zeiten wirklich interessieren.

So absoluten Speed wie du ihn für deine LED Effekte benötigst, werde ich nicht benötigen. Da würde ich dann eher zu einem schnellerem µC greifen. :slight_smile:

Ich auch. :wink: Nach ein paar Jahren mit Arduinos und Teensy ist nun die NodeMCU unterwegs zu mir.
Combie hat mich überzeugt und ich habe mich schon lange nicht mehr so auf ein Paket gefreut.

Gruß,

Helmuth

Hallo,

ich habe mich mal an einem Benchmark versucht. Ich hoffe der Code ist so wie gewollt richtig.

Bei einem Inkrement bis 4 Millionen ist das vernachlässigbar.
Da steht es überraschenderweise
2.766.900 µs zu ++ lokal
2.767.032 µs ++ global zu gunsten der lokalen Addition ?

Was aber massiv Zeit kostet sind statische lokale Variablen
15.092.280 µs zu Counter und ++ lokal
19.116.956 µs Counter lokal, ++ global

+/- 4µs bekannte Ungenauigkeit

unsigned long MAX = 4000000;
unsigned long BenchmarkCounter;
int global_eins = 1;

unsigned long start;

void setup() {
  Serial.begin(9600);

}

void loop() {

  start = micros();             
  while (BenchmarkCounter < MAX)  {
    BenchmarkCounter = Benchmark_1(BenchmarkCounter);
  } 
  Serial.print( micros() - start ); Serial.println (" ++ lokal"); 

  BenchmarkCounter = 0;
  
  start = micros();             
  while (BenchmarkCounter < MAX)  {
    BenchmarkCounter = Benchmark_2(BenchmarkCounter);
  } 
  Serial.print( micros() - start ); Serial.println (" ++ global"); 
  
  BenchmarkCounter = 0;
  
  start = micros();             
  while (BenchmarkCounter < MAX)  {
    BenchmarkCounter = Benchmark_3();
  } 
  Serial.print( micros() - start ); Serial.println (" Counter und ++ lokal"); 
  
  BenchmarkCounter = 0;

  start = micros();             
  while (BenchmarkCounter < MAX)  {
    BenchmarkCounter = Benchmark_4();
  } 
  Serial.print( micros() - start ); Serial.println (" Counter lokal, ++ global"); 
  
  BenchmarkCounter = 0;
  
  Serial.println(); Serial.println();
  delay(5000);
}


unsigned long Benchmark_1 (unsigned long Counter)
{
  int lokal_eins = 1;
  Counter += lokal_eins;
  return Counter; 
}

unsigned long Benchmark_2 (unsigned long Counter)
{
  Counter += global_eins;
  return Counter; 
}

unsigned long Benchmark_3 ()
{
  static unsigned long L_Counter = 0;
  int lokal_eins = 1;
  L_Counter += lokal_eins;
  return L_Counter; 
}

unsigned long Benchmark_4 ()
{
  static unsigned long L_Counter = 0;
  L_Counter += global_eins;
  return L_Counter; 
}

Hallo,

im Grunde genommen wäre so ein ESP8266 die Power Variante als Ersatz für den Mini, Micro, Nano usw. Könnte Arduino/Genuino eigentlich auch unter eigenen Namen rausbringen. :slight_smile: IDE Addon gibts ja schon wie ich sehe. Nur muß man auf die schon gewohnten AVR Befehle verzichten.

Sehr interessant! Da scheint etwas in den Funktionen komplett anders zu laufen.
Werd mir mal das Disassembly anschauen ...

Mach mal inzwischen Benchmark_3 und _4 zu inline Funktionen :wink:

Aus diesem Code entfernt der Compiler aller Wahrscheinlichkeit nach die lokale Variable:

unsigned long Benchmark_1 (unsigned long Counter)
{
  int lokal_eins = 1;
  Counter += lokal_eins;
  return Counter;
}

hi,

ich weiß ja nicht, wie schlau der compiler optimiert, aber wäre nicht auch möglich, daß er gleich die funktion wegoptimiert und ein

BenchmarkCounter++;
draus macht?

Das halte ich für durchaus denkbar.
Die Optimierung innerhalb der Funktion erfolgt ziemlich sicher.

Hallo,

habe den "Einwand" von eisbär mal fast so eingebaut. Nr. 0
Ist am schnellsten, beeinflußt jedoch den 2. Benchmark der vorher der erste war, der nun etwas langsamer. ?

2.766.900 ++ lokal optimiert
2.767.112 ++ lokal (vorher 2.766.900 µs)
2.767.028 ++ global
15.092.288 Counter und ++ lokal
19.116.952 Counter lokal, ++ global

unsigned long MAX = 4000000;
unsigned long BenchmarkCounter;
int global_eins = 1;

unsigned long start;

void setup() {
  Serial.begin(9600);

}

void loop() {

  start = micros();             
  while (BenchmarkCounter < MAX)  {
    BenchmarkCounter = Benchmark_0(BenchmarkCounter);
  } 
  Serial.print( micros() - start ); Serial.println (" ++ lokal optimiert"); 

  BenchmarkCounter = 0;

  start = micros();             
  while (BenchmarkCounter < MAX)  {
    BenchmarkCounter = Benchmark_1(BenchmarkCounter);
  } 
  Serial.print( micros() - start ); Serial.println (" ++ lokal"); 

  BenchmarkCounter = 0;
  
  start = micros();             
  while (BenchmarkCounter < MAX)  {
    BenchmarkCounter = Benchmark_2(BenchmarkCounter);
  } 
  Serial.print( micros() - start ); Serial.println (" ++ global"); 
  
  BenchmarkCounter = 0;
  
  start = micros();             
  while (BenchmarkCounter < MAX)  {
    BenchmarkCounter = Benchmark_3();
  } 
  Serial.print( micros() - start ); Serial.println (" Counter und ++ lokal"); 
  
  BenchmarkCounter = 0;

  start = micros();             
  while (BenchmarkCounter < MAX)  {
    BenchmarkCounter = Benchmark_4();
  } 
  Serial.print( micros() - start ); Serial.println (" Counter lokal, ++ global"); 
  
  BenchmarkCounter = 0;
  
  Serial.println(); Serial.println();
  delay(5000);
}


unsigned long Benchmark_0 (unsigned long Counter)
{
  Counter++;
  return Counter; 
}

unsigned long Benchmark_1 (unsigned long Counter)
{
  int lokal_eins = 1;
  Counter += lokal_eins;
  return Counter; 
}

unsigned long Benchmark_2 (unsigned long Counter)
{
  Counter += global_eins;
  return Counter; 
}

unsigned long Benchmark_3 ()
{
  static unsigned long L_Counter = 0;
  int lokal_eins = 1;
  L_Counter += lokal_eins;
  return L_Counter; 
}

unsigned long Benchmark_4 ()
{
  static unsigned long L_Counter = 0;
  L_Counter += global_eins;
  return L_Counter; 
}
2767028 ++ global
2767112 ++ lokal
-------
     84

µS Differenz = 1344 Takte bei 16MHz

Kann kaum von einem Unterschied kommen, der 4.000.000 mal dran kommt...

hi,

mach doch auch noch:

  start = micros();             
  while (BenchmarkCounter < MAX)  {
    BenchmarkCounter++;
  } 
  Serial.print( micros() - start ); Serial.println (" ++ lokal optimiert");

wenn das dann auch im bereich der ersten drei messungen liegt, kann man sicher sein, daß er in diesen drei fällen gar keine funktion aufruft, sondern diese komplett wegoptimiert...

gruß stefan

Hallo,

die Werte haben sich erst mit dem eingefügten Bechmark 0 umgedreht. Jetzt steht es 84µs zu gunsten von lokal.
Ja, das ist vernachlässigbar und kann nicht auf den Funktionsunterschied beruhen.
Daher sollte die Frage ob lokal oder global im Grunde geklärt sein. Spielt keine Rolle.
Interessantes Nebenergebnis ist jedoch die massive Zeitzunahme mit statischen lokalen Variablen.
Wer Speed braucht sollte die zu globalen machen.

Was macht deine inline Funktion?

Eisebaer:
hi,

mach doch auch noch:
.........
wenn das dann auch im bereich der ersten drei messungen liegt, kann man sicher sein, daß er in diesen drei fällen gar keine funktion aufruft, sondern diese komplett wegoptimiert...

gruß stefan

Hallo,

jawohl Chef :slight_smile:

Ergebnis wie du geahnst hast.

2.766.900 ++ in while/loop
2.767.096 ++ lokal optimiert
2.767.112 ++ lokal
2.767.032 ++ global

Der Compiler ist richtig schlau. :slight_smile:

Beeinflußt jedoch immer die nachfolgende Aktion. Scheinbar wegen dem Nullen des Counters. Die erste lokale ist immer die schnellste.

Um ein Inlining wirklich zu erlauben, sollten die Funktionen static sein.

When an inline function is not static, then the compiler must assume that there may be calls from other source files; since a global symbol can be defined only once in any program, the function must not be defined in the other source files, so the calls therein cannot be integrated. Therefore, a non-static inline function is always compiled on its own in the usual fashion.

Interessantes Nebenergebnis ist jedoch die massive Zeitzunahme mit statischen lokalen Variablen.
Wer Speed braucht sollte die zu globalen machen.

Sicher Falsch :wink:

Wer Speed braucht, muss genauer schauen.

Dass da was ganz anderes abläuft (z.B. echte Funktion statt zu Counter++; optimiert), liegt sicher nur indirekt an der statischen lokalen Variablen. In echten Fällen, wo bei beiden Tests vergleichbarer Code erzeugt wird, ist der Unterschied globale / statische Variable vermutlich auch eher 0 als Faktor 10).

hi,

mach doch auch noch

verbessere ich gaaanz schnell auf:

mach doch bitte auch noch

war im eifer...

gruß stefan

Die erste lokale ist immer die schnellste.

Jetzt schon 3 Mal, das ist der Beweis
(wofür? dass die serielle Ausgabe im Hintergrund Zeit kostet? andere Interrupts?)

Warte mit jedem Test, bis das vorige Serial.print sicher fertig ist und millis() sich gerade verändert hat...
Mach mal
unsigned long dauer = micros() - start;
bevor du Serial.print aufrufst, und warte 3 oder 10 Durchläufe von loop() ab.
Vermutlich erhältst du noch mehr unterschiedliche Zahlen...

Alternativ kannst du auch umgekehrt testen: Wie weit kommt dein Counter in genau 5000 ms?

Wenn du willst , natürlich nur :wink:

Der Modifizierer "static" hat (AFAIR 3) unterschiedliche Bedeutungen, je nach dem wo er im Code steht.

Lokale static Variablen liegen wie globale Variablen im RAM, dazwischen sollte es also keine Zeitunterschiede geben.
Lokale non-static Variablen kann der Compiler in Register legen, was zu schnellerem Code führt, da die Werte nicht bei jeder Änderung aus dem RAM in Register geladen, geändert und wieder zurückgeschrieben werden müssen.

Bei Deklarationen (Funktionen oder Variablen) ist static eine Anweisung an den Linker. Non-static Elemente liegen nur einmal im Speicher, und werden von allen Teilen des Programms gemeinsam benutzt. Static Elemente hingegen können mehrfach im Speicher liegen, d.h. Modul A kann eine andere Funktion F benutzen als Modul B, obwohl beide F heißen.

Hallo,

Moment, nochmal langsam. Ich teste einmal mit static lokalen und einmal mit globalen Counter. Da tritt ein massiver Zeitunterschied auf. Also können statische lokale nicht zu globalen optimiert werden seitens des Compilers. Sonst wäre das genauso schnell wie ein anderer Testlauf vorher.

Ich weis nicht worauf ihr hinaus wollt. Es ist doch so wie es eben ist.

@ eisbär
keine Sorge, dass bitte habe ich automatisch rein optimiert ... :slight_smile:

Edit:
wenn ich nach der seriellen Ausgabe ein delay einbaue sind die ersten 4 Tests alle gleich. +/- bedingten 4µs eben.

2.766.900 µs
2.766.896
2.766.900
2.766.896