Zeitmessung mit micros()

Hallo,
nachdem ich festgestellt habe, dass ich in diesem Forum viele gute Tipps bekomme möchte ich hier folgende Frage stellen.
Kann ich mit der Funktion millis() die Ausführungszeit einer Routine messen?
Ich habe folgendes Programm geschieben:

unsigned long StartZeit, EndZeit, DiffZeit;
void setup() {
  
  // put your setup code here, to run once:
Serial.begin(9600);
}

void loop() {
  StartZeit = micros();
  LeereSchleife();
  EndZeit   = micros();
  DiffZeit = EndZeit - StartZeit;
  Serial.print("Start Zeit = ");
  Serial.println(StartZeit);
  Serial.print("EndZeit = ");
  Serial.println(EndZeit);
  Serial.print("Zeit in mySek = ");
  Serial.println(DiffZeit);
  // put your main code here, to run repeatedly:

}

void LeereSchleife()
{
  long int i=0, zaeh = 1800;
  float phi, x, y, pi;
  Serial.print("Start Zaehler = ");
  Serial.println(i);
  pi = asin(0.5)*6.0;
  Serial.println(pi,5);
  do
  {
    i++;
    phi = float(i)*pi/360.0;
    x = cos(phi);
    y = sin(phi);
    
  } while (i<=zaeh);
  Serial.print("Zaehler = ");
  Serial.println(i);
}

Was mir dabei auffällt: Egal wie groß der Zähler ist bekomme ich immer annähernd gleiche Zeiten heraus.
Für :
zaeh = 1800 → 45760 μs
zaeh = 90 → 43680 μs
Kann das sein, dass trotz der Komplexität der Funktion ein Unterschied von 2080 μs ist?
Und dass da ein overhaed von ca. 42000 μs ist? Wo könnte der herkommen?
Vielen Dank im Vorraus!

Naja, zumindest die seriellen Ausgaben sind ja unterschiedlich lang, das geht halt auf die Laufzeit.

Nimm die doch mal raus und schau Dir das Ergebnis an.

Was mir dabei auffällt: Egal wie groß der Zähler ist bekomme ich immer annähernd gleiche Zeiten heraus.

Natürlich hat die Anzahl der Schleifendurchläufe keinen Einfluss auf die Laufzeit.

Der Grund ist ganz einfach und naiv!
Diese Variablen float phi, x, y werden nirgendwo verwertet.
Also darf der Code Optimierer sie rückstandslos entsorgen.
Und die damit nutzlose Schleife gleich mit.

Und dass da ein overhaed von ca. 42000 μs ist? Wo könnte der herkommen?

Dass du die Serielle, nicht aus dem Auge verlieren solltest, wurde dir ja schon gesagt.
Sobald deren FiFo voll ist, blockiert Print und diese Blockadezeiten gehen in die Messung ein.

Hallo Klaus_ww und comby,
danke für Eure Antwort.
Aber ich habe da mal etwas rumgespielt und die Zeitmassung direkt vor und nach der do-Schleife gesetzt. Da kommen ganz komische Zeiten raus. Ergebnis = 4 μs. Egal wie groß der Zähler ist. Kann es sein, das der Kompiler den Code optimiert und so die do-Schleife nicht abgearbeitet wird?
Wenn ich den Wert x nach Beendigung der Schleife ausdrucke dann kommen plötzlich Laufzeiten heraus, die vernünftig erscheinen.
Kann ich die Optimierung des Compilers abschalten?

Kann es sein, das der Kompiler den Code optimiert und so die do-Schleife nicht abgearbeitet wird?

Genau das habe ich dir doch gerade gesagt!

Kann ich die Optimierung des Compilers abschalten?

Ja klar!
Es gibt sicherlich 1/2 Dutzend Möglichkeiten.
Aber warum sollte man das tun?

Hallo,
vielen Dank für Eure Antworten.
Ich will schlicht und einfach die Laufzeit meines Programmes messen.
Ich benutze die Arduino IDE 1.8.8.
Kann man da die Codeoptimierung abschalten und wo finde ich den Schalter?

Die Optimierungsstufe wird in der betreffenden boards.txt festgelegt.

Generell kann ich meinen Vorrednern nur zustimmen!

Jedoch braucht man m.E. keine “globale” Änderung vornehmen, wenn man lediglich innerhalb einer einzigen Funktion diese Optimierung (aus welchem Grund auch immer…) abschalten möchte: Ich setze in solchen Schleifen einen winzigen Bruchteil aus “Inline-Assembler” ein … und schon wird an dieser Stelle nichts mehr wegoptimiert.

Bleiben immer noch die Warnungen des Compilers, weil Variablen nicht verwendet werden. Diese bette ich in eine “(un)sinnige Funktion” ein und gebe sie an das aufrufende Programm zurück (auch wenn ich davon nichts verwende). Dann sind auch die Warnungen weg - und das Programm liefert deutlich unterschiedliche Mess-Ergebnisse.

Der leicht veränderte Sketch:

unsigned long StartZeit, EndZeit, DiffZeit;
void setup() {
	
Serial.begin(9600);
}

void loop() {
  StartZeit = micros();
  LeereSchleife();                    
  EndZeit   = micros();
  DiffZeit = EndZeit - StartZeit;
  
  Serial.print("Start Zeit = ");
  Serial.println(StartZeit);
  Serial.print("EndZeit = ");
  Serial.println(EndZeit);
  Serial.print("Zeit in mySek = ");
  Serial.println(DiffZeit);

  while( true ) {}                     //  <<<   STOPP !!
}

float LeereSchleife()                  //  <<<   Float-Rückgabe w/ "return z"
{
  long int i = 0;
  
  long int zaeh = 5000;               //  <<<   Bei Bedarf ändern...
  
  float phi, x, y, z, pi;
  pi = asin(0.5)*6.0;
  do
  {
    asm volatile( "NOP" );             //  <<<   Inline Assembler
  	  	
    i++;
    phi = float(i)*pi/360.0;
    x = cos(phi);
    y = sin(phi);
    
    z = x + y;                         //  <<<   Ergebnis "sinnvoll" verwenden ...
   
  } while (i<=zaeh);
  
  return z;                            //  <<<   zurückgeben
}

Hallo RudiDL5,
vielen Dank für Deine Antwort.
Ihre Antwort ist echt klasse.
Hat mir sehr geholfen!
Gruß
Kurt

Prima!
Danke für die Rückmeldung :slight_smile:

Es reicht auch die Variablen als volatile zu deklarieren.

volatile float phi, x, y, z, pi;

Dann wird immer noch weitestgehend optimiert, aber die Variablen werden korrekt berechnet und die Schleife läuft durch.

Vollständiges Optimierungsverbot für eine Funktion:

unsigned long StartZeit, EndZeit, DiffZeit;
void setup() 
{
  Serial.begin(9600);
}

void loop() 
{
  StartZeit = micros();
  LeereSchleife();
  EndZeit   = micros();
  DiffZeit = EndZeit - StartZeit;
  Serial.print("Start Zeit = ");
  Serial.println(StartZeit);
  Serial.print("EndZeit = ");
  Serial.println(EndZeit);
  Serial.print("Zeit in mySek = ");
  Serial.println(DiffZeit);

  Serial.print(" ---------- ");

  delay(1000); // gibt der Seriellen Zeit ihre Ausgaben zu tätigen
}

#pragma GCC push_options
#pragma GCC optimize ("-O0")
void LeereSchleife()
{
  long int i=0, zaeh = 1800;
  float phi, x, y, pi; // Code wird automatisch optimiert
  Serial.print("Start Zaehler = ");
  Serial.println(i);
  pi = asin(0.5)*6.0;
  Serial.println(pi,5);
  do
  {
    i++;
    phi = float(i)*pi/360.0;
    x = cos(phi);
    y = sin(phi);
    
  } while (i<=zaeh);
  Serial.print("Zaehler = ");
  Serial.println(i);
}
#pragma GCC pop_options

Ist ja noch besser - Super!
So lernt man(n) immer wieder hinzu, Danke :slight_smile: