Die Funktion millis
würde in einer Zeitspanne von mehr als 100 Mikrosekunden oder weniger ausgeführt. Gibt es einen zuverlässigen Weg, um die Zeit zu messen, die ein einzelner Millis-Aufruf benötigt?
Ein Ansatz, der in den Sinn kommt micros
, besteht darin, einen Aufruf von zu verwenden micros
, der auch die vom Funktionsaufruf micros
selbst benötigte Zeit einschließt. Je nachdem, wie lange Mikros dauern, kann die Messung für millis
ausgeschaltet sein.
Ich muss dies finden, da eine Anwendung, an der ich arbeite, genaue Zeitmessungen für jeden Schritt im Code erfordert, einschließlich millis
.
miilis
dauert.Antworten:
Wenn Sie genau wissen möchten, wie lange etwas dauern wird, gibt es nur eine Lösung: Schauen Sie sich die Demontage an!
Beginnend mit dem Minimalcode:
Dieser Code, der kompiliert und dann eingegeben wird,
avr-objdump -S
erzeugt eine dokumentierte Demontage. Hier sind die interessanten Auszüge:void loop()
produziert:Was ist ein Funktionsaufruf (
call
), vier Kopien (die jedes der Bytes imuint32_t
Rückgabewert von kopierenmillis()
(beachten Sie, dass die Arduino-Dokumente dies als ein bezeichnenlong
, aber sie sind falsch, um die Variablengrößen nicht explizit anzugeben)) und schließlich die Funktionsrückgabe.call
erfordert 4 Taktzyklen und jeweilssts
2 Taktzyklen, sodass wir nur für den Funktionsaufruf-Overhead ein Minimum von 12 Taktzyklen haben.Schauen wir uns nun die Demontage der
<millis>
Funktion an, die sich unter folgender Adresse befindet0x14e
:Wie Sie sehen, ist die
millis()
Funktion ziemlich einfach:in
speichert die Einstellungen des Interrupt-Registers (1 Zyklus)cli
schaltet die Interrupts aus (1 Zyklus)lds
Kopiere eines der 4 Bytes des aktuellen Wertes des Millizählers in ein temporäres Register (2 Taktzyklen)lds
Byte 2 (2 Taktzyklen)lds
Byte 3 (2 Taktzyklen)lds
Byte 4 (2 Taktzyklen)out
Interrupt-Einstellungen wiederherstellen (1 Taktzyklus)movw
Shuffle-Register um (1 Taktzyklus)movw
und wieder (1 Taktzyklus)ret
Rückkehr vom Unterprogramm (4 Zyklen)Wenn wir also alle addieren, haben wir insgesamt 17 Taktzyklen in der
millis()
Funktion selbst plus einen Aufrufoverhead von 12 für insgesamt 29 Taktzyklen.Unter der Annahme einer 16-MHz-Taktrate (die meisten Arduinos) beträgt jeder Taktzyklus
1 / 16e6
Sekunden oder 0,0000000625 Sekunden, was 62,5 Nanosekunden entspricht. 62,5 ns * 29 = 1,812 Mikrosekunden.Daher beträgt die Gesamtausführungszeit für einen einzelnen
millis()
Aufruf bei den meisten Arduinos 1,812 Mikrosekunden .AVR Assembly Referenz
Als Randnotiz gibt es hier Raum für Optimierung! Wenn Sie die
unsigned long millis(){}
Funktionsdefinition so aktualisiereninline unsigned long millis(){}
, dass sie aktuell ist , werden Sie den Anrufoverhead (auf Kosten einer geringfügig größeren Codegröße) entfernen . Außerdem sieht es so aus, als würde der Compiler zwei unnötige Schritte ausführen (die beidenmovw
Aufrufe, aber ich habe es mir nicht so genau angesehen).Wirklich, wenn man bedenkt, dass der Funktionsaufruf-Overhead 5 Anweisungen umfasst und der tatsächliche Inhalt der
millis()
Funktion nur 6 Anweisungen umfasst, denke ich, dass diemillis()
Funktion eigentlichinline
standardmäßig sein sollte, aber die Arduino-Codebasis ist ziemlich schlecht optimiert.Hier ist die vollständige Disassemby für alle Interessierten:
quelle
sts
Werte sollten nicht als Verbindungsaufwand gezählt werden: Dies sind die Kosten für die Speicherung des Ergebnisses in einer flüchtigen Variablen, die Sie normalerweise nicht durchführen würden. 2) Auf meinem System (Arduino 1.0.5, gcc 4.8.2) habe ich dasmovw
s nicht. Dann betragen diemillis()
Gesprächskosten: 4 Overhead-Zyklen + 15 Zyklen anmillis()
sich = 19 Zyklen insgesamt (≈ 1,188 µs bei 16 MHz).x
ist einuint16_t
. Es sollten höchstens 2 Exemplare sein, wenn dies die Ursache ist. Wie auch immer, die Frage ist, wie lange es dauertmillis()
, wenn es verwendet wird , und nicht, wenn es aufgerufen wird, während das Ergebnis ignoriert wird. Da bei jeder praktischen Verwendung etwas mit dem Ergebnis zu tun ist, habe ich das Speichern des Ergebnisses über erzwungenvolatile
. Normalerweise würde der gleiche Effekt durch die spätere Verwendung der Variablen erzielt, die auf den Rückgabewert des Anrufs gesetzt ist, aber ich wollte nicht, dass dieser zusätzliche Anruf Platz in der Antwort beansprucht.uint16_t
in der Quelle stimmt nicht mit der Assembly überein (4 Bytes im RAM gespeichert). Sie haben wahrscheinlich die Quelle und die Zerlegung von zwei verschiedenen Versionen veröffentlicht.Schreiben Sie eine Skizze, die 1000-mal millis, nicht durch Erstellen einer Schleife, sondern durch Kopieren und Einfügen. Messen Sie das und vergleichen Sie es mit der tatsächlich erwarteten Zeit. Beachten Sie, dass die Ergebnisse bei verschiedenen Versionen der IDE (und insbesondere des Compilers) variieren können.
Eine andere Möglichkeit besteht darin, einen E / A-Pin vor und nach dem Millis-Aufruf umzuschalten und dann die Zeit für einen sehr kleinen Wert und einen etwas größeren Wert zu messen. Vergleichen Sie die gemessenen Timings und berechnen Sie den Overhead.
Der genaueste Weg ist ein Blick auf die Disassemblierungsliste, den generierten Code. Aber das ist nichts für schwache Nerven. Sie müssen das Datenblatt sorgfältig studieren, wie lange jeder Unterrichtszyklus dauert.
quelle
millis()
Anrufe benötigen?delay
, du hast recht. Die Idee bleibt jedoch die gleiche, Sie können eine große Anzahl von Anrufen zeitlich festlegen und diese mitteln. Das globale Ausschalten der Interrupts ist jedoch möglicherweise keine gute Idee; o)Ich rufe Millis zum zweiten Mal auf und vergleiche dann die tatsächlichen mit den erwarteten.
Es wird einen minimalen Overhead geben, der jedoch an Bedeutung verliert, je öfter Sie millis () aufrufen.
Wenn du siehst
Sie können sehen, dass millis () bei nur 4 Anweisungen
(cli is simply # define cli() \__asm__ \__volatile__ ("cli" ::))
und einer Rückkehr sehr klein ist .Ich würde es ungefähr 10 Millionen Mal nennen, wenn ich eine FOR-Schleife benutze, die eine volatile als Bedingung hat. Das Schlüsselwort volatile verhindert, dass der Compiler eine Optimierung der Schleife selbst versucht.
Ich garantiere nicht, dass das Folgende syntaktisch perfekt ist.
Ich vermute, das dauert ~ 900ms oder ungefähr 56us pro Anruf nach Millis. (Ich habe keinen Aruduino-Geldautomaten.
quelle
int temp1,temp2;
umvolatile int temp1,temp2;
zu verhindern, dass der Compiler sie möglicherweise entfernt optimiert.