Ist es möglich, einen Codeblock zu überwachen und die Anzahl der Prozessortaktzyklen zu bestimmen, die der Code auf einem Arduino- und/oder AVR-Atmel-Prozessor benötigte? oder sollte ich lieber die vor und nach der Codeausführung verstrichenen Mikrosekunden überwachen? Hinweis: Ich interessiere mich nicht so sehr für die Echtzeit (wie viele echte Sekunden vergangen sind) wie für "Wie viele Taktzyklen benötigt dieser Code von der CPU?"
Die aktuelle Lösung, die ich mir einfallen lassen kann, stammt von time.c:
#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
Wiring.c fügt hinzu:
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )
Durch dieses Konto könnte ich Taktzyklen berechnen, die durch die Überwachung der verstrichenen Mikrosekunden vergangen sind, und diese dann an microsecondsToClockCycles () übergeben. Meine Frage ist, gibt es einen besseren Weg?
Nebenbemerkung: Gibt es gute Ressourcen für die Leistungsüberwachung des AVR. Die Suche auf lmgtfy.com und in verschiedenen Foren liefert keine offensichtlichen Ergebnisse, außer der Erforschung von Timern
Danke
Die einfachste Methode besteht darin, Ihren Code dazu zu bringen, einen Pin nach oben zu ziehen, bevor er den Code ausführt, den Sie timen möchten, und ihn nach unten zu ziehen, nachdem er fertig ist, was auch immer. Machen Sie dann die Code-Schleife (oder verwenden Sie ein digitales Oszilloskop mit Speicher im Single-Shot-Modus) und Scope dann Pin. Die Länge des Impulses sagt Ihnen, wie lange es gedauert hat, den Codeabschnitt plus einen Taktzyklus von der Änderung des Pin-Status auszuführen (ich denke, es dauert einen Zyklus, nicht 100% sicher).
Was meinst du mit "überwachen"?
Es sollte nicht schwer sein, Taktzyklen für AVR für kleine Teile des Assemblercodes zu zählen.
Sie können auch einen Port festlegen, bevor der Code ausgeführt wird, und ihn danach zurücksetzen und dies mit einem Logikanalysator oder einem Oszilloskop überwachen, um das Timing zu erhalten.
Und Sie könnten die Zeit auch von einem schnell laufenden Timer ablesen, wie Sie sagen.
Dies ist ein Beispiel für Arduino, das die Funktion clockCyclesPerMicrosecond() verwendet, um die abgelaufenen Uhren zu berechnen. Dieser Code wartet 4 Sekunden und gibt dann die Zeit aus, die seit dem Start des Programms vergangen ist. Die 3 Werte links sind die Gesamtzeit (Mikrosekunden, Millisekunden, Gesamttaktzyklen) und die 3 Werte ganz rechts sind die verstrichenen Zeiten:
Ausgabe:
clocks for 1us:16
runtime us, ms, ck :: elapsed tme us, ms ck
4003236 4002 64051776 :: 4003236 4002 64051760
8006668 8006 128106688 :: 4003432 4004 64054912
12010508 12010 192168128 :: 4003840 4004 64061440
16014348 16014 256229568 :: 4003840 4004 64061440
20018188 20018 320291008 :: 4003840 4004 64061440
24022028 24022 384352448 :: 4003840 4004 64061440
28026892 28026 448430272 :: 4004864 4004 64077824
32030732 32030 512491712 :: 4003840 4004 64061440
36034572 36034 576553152 :: 4003840 4004 64061440
40038412 40038 640614592 :: 4003840 4004 64061440
44042252 44042 704676032 :: 4003840 4004 64061440
48046092 48046 768737472 :: 4003840 4004 64061440
52050956 52050 832815296 :: 4004864 4004 64077824
Ich bin mir sicher, dass es eine vernünftige Erklärung gibt, warum die ersten Schleifen auch kürzere verstrichene Taktzyklen hatten als die meisten und warum alle anderen Schleifen zwischen zwei Längen von Taktzyklen umschalten.
Code:
unsigned long us, ms, ck;
unsigned long _us, _ms, _ck;
unsigned long __us, __ms, __ck;
void setup() {
Serial.begin(9600);
}
boolean firstloop=1;
void loop() {
delay(4000);
if (firstloop) {
Serial.print("clocks for 1us:");
ck=microsecondsToClockCycles(1);
Serial.println(ck,DEC);
firstloop--;
Serial.println("runtime us, ms, ck :: elapsed tme us, ms ck");
}
_us=us;
_ms=ms;
_ck=ck;
us=micros(); // us since program start
ms=millis();
//ms=us/1000;
ck=microsecondsToClockCycles(us);
Serial.print(us,DEC);
Serial.print("\t");
Serial.print(ms,DEC);
Serial.print("\t");
Serial.print(ck,DEC);
Serial.print("\t::\t");
__us = us - _us;
__ms = ms - _ms;
__ck = ck - _ck;
Serial.print(__us,DEC);
Serial.print("\t");
Serial.print(__ms,DEC);
Serial.print("\t");
Serial.println(__ck,DEC);
}
Nebenbemerkung: Wenn Sie die 4-Sekunden-Verzögerung entfernen, werden Sie die Auswirkungen von Serial.print() viel deutlicher sehen. Beachten Sie, dass hier 2 Läufe verglichen werden. Ich habe nur 4 Proben nebeneinander aus ihren jeweiligen Protokollen aufgenommen.
Lauf 1:
5000604 5000 80009664 :: 2516 2 40256
6001424 6001 96022784 :: 2520 3 40320
7002184 7002 112034944 :: 2600 3 41600
8001292 8001 128020672 :: 2600 3 41600
Lauf 2:
5002460 5002 80039360 :: 2524 3 40384
6000728 6000 96011648 :: 2520 2 40320
7001452 7001 112023232 :: 2600 3 41600
8000552 8000 128008832 :: 2604 3 41664
Die verstrichene Zeit erhöht sich über die Gesamtlaufzeit. Nach Ablauf einer Sekunde steigen die Takte im Durchschnitt von 40k auf 44k. Dies geschieht konsistent einige Millisekunden nach 1 Sekunde und die verstrichenen Takte bleiben mindestens für die nächsten 10 Sekunden bei etwa 44k (ich habe es nicht weiter getestet). Aus diesem Grund ist eine Überwachung sinnvoll oder erforderlich. Vielleicht hat die verringerte Effizienz mit der Konfiguration oder Fehlern in der Serie zu tun? Oder vielleicht verwendet der Code den Speicher nicht richtig und hat ein Leck, das sich auf die Leistung auswirkt usw.
Da sich jede Ihrer Quelle hinzugefügte Codezeile auf die Leistung auswirkt und angewendete Optimierungen ändern könnte. Die Änderungen sollten das Minimum sein, das zum Ausführen der Aufgabe erforderlich ist.
Ich habe gerade ein Atmel Studio-Plugin namens "Annotated Assembly File Debugger" gefunden. http://www.atmel.com/webdoc/aafdebugger/pr01.html Es scheint, als würde Ihnen das schrittweise Durchlaufen der tatsächlich generierten Assemblersprache, obwohl es wahrscheinlich langweilig ist, genau zeigen, was passiert. Möglicherweise müssen Sie noch entschlüsseln, wie viele Zyklen für jede Anweisung benötigt werden, aber es würde viel näher kommen als einige der anderen geposteten Optionen.
Für diejenigen, die es nicht wissen, im Ausgabeordner Ihres Projekts befindet sich eine Datei mit einer LSS-Erweiterung. Diese Datei enthält Ihren gesamten ursprünglichen Quellcode als Kommentare und unter jeder Zeile befindet sich die Assemblersprache, die basierend auf dieser Codezeile generiert wurde. Das Generieren der LSS-Datei kann deaktiviert werden, überprüfen Sie daher die folgende Einstellung.
Projekteigenschaften | Werkzeugkette | AVR/GNU Allgemein | Ausgabedateien
Checkbox ".lss (lss-Datei generieren)
Sie könnten einen der eingebauten Timer verwenden. Richten Sie vor dem Block alles für prescaller=1 und TCNT=0 ein. Aktivieren Sie dann den Timer in der Zeile vor dem Block und deaktivieren Sie ihn in der Zeile nach dem Block. Der TCNT hält nun die Anzahl der Zyklen, die der Block benötigt hat, abzüglich der festen Zyklen für den Aktivierungs- und Deaktivierungscode.
Beachten Sie, dass der TNCT bei einem 16-Bit-Zeitgeber nach 65535 Taktzyklen überläuft. Sie können das Overflow-Flag verwenden, um die Laufzeit zu verdoppeln. Wenn Sie dennoch länger brauchen, können Sie einen Prescaler verwenden, erhalten jedoch eine geringere Auflösung.
Cyphunk