Ich schreibe ein Programm für einen ATmega 328 mit 16 MHz (es ist ein Arduino Duemilanove, wenn Sie sie kennen, es ist ein AVR-Chip).
Ich habe einen Interrupt-Prozess, der alle 100 Mikrosekunden ausgeführt wird. Ich würde sagen, es ist unmöglich herauszufinden, wie viel "Code" Sie in einer Schleife von 100 Mikrosekunden ausführen können (ich schreibe in C, das vermutlich dann in Assembly in ein Binärbild konvertiert wird?).
Dies würde auch von der Komplexität des Codes abhängen (ein riesiger Einzeiler könnte beispielsweise langsamer laufen als mehrere kurze Zeilen).
Ist mein Verständnis insofern richtig, als mein Prozessor mit einer Taktrate oder 16 MHz 16 Millionen Zyklen pro Sekunde ausführt (dies bedeutet 16 Zyklen pro Mikrosekunde 16.000.000 / 1.000 / 1.000)? Wenn ich also in meiner 100-Mikrosekunden-Schleife mehr tun möchte, würde der Kauf eines schnelleren Modells wie einer 72-MHz-Version 72 Zyklen pro Mikrosekunde (72.000.000 / 1.000 / 1.000) ergeben?
Derzeit läuft es nur ein bisschen zu langsam, dh es dauert etwas länger als 100 Mikrosekunden, um die Schleife durchzuführen (wie lange genau ist zu schwer zu sagen, aber es fällt allmählich zurück), und ich möchte, dass es etwas mehr macht, ist Das ist ein vernünftiger Ansatz, um einen schnelleren Chip zu bekommen, oder bin ich verrückt geworden?
quelle
Antworten:
Im Allgemeinen hängt die Anzahl der Montageanweisungen, die das Gerät pro Sekunde ausführen kann, von der Befehlsmischung und der Anzahl der Zyklen ab, die jeder Befehlstyp (CPI) zur Ausführung benötigt. Theoretisch können Sie Ihren Code zyklisch zählen, indem Sie sich die zerlegte asm-Datei und die Funktion ansehen, um die Sie sich kümmern, alle verschiedenen Arten von Anweisungen darin zählen und die Zykluszahlen aus dem Datenblatt Ihres Zielprozessors nachschlagen.
Das Problem der Bestimmung der effektiven Anzahl von Befehlen pro Sekunde wird bei komplexeren Prozessoren durch die Tatsache verschärft, dass sie über Pipelines verfügen und Caches haben und was nicht. Dies ist bei einem einfachen Gerät wie einem ATMega328 nicht der Fall, bei dem es sich um eine einzelne Anweisung im Flugprozessor handelt.
In der Praxis wäre meine Antwort für ein einfaches Gerät wie einen AVR mehr oder weniger "Ja". Das Verdoppeln Ihrer Taktrate sollte die Hälfte der Ausführungszeit einer bestimmten Funktion betragen. Bei einem AVR laufen sie jedoch nicht schneller als 20 MHz, sodass Sie Ihr Arduino nur um weitere 4 MHz "übertakten" können.
Dieser Rat gilt nicht für einen Prozessor mit erweiterten Funktionen. Durch das Verdoppeln der Taktrate auf Ihrem Intel-Prozessor wird in der Praxis nicht die Anzahl der Anweisungen verdoppelt, die pro Sekunde ausgeführt werden (aufgrund von Verzweigungsfehlvorhersagen, Cache-Fehlern usw.).
quelle
Die Antwort von @ vicatcu ist ziemlich umfassend. Eine weitere zu beachtende Sache ist, dass die CPU beim Zugriff auf E / A, einschließlich Programm- und Datenspeicher, in Wartezustände (blockierte CPU-Zyklen) geraten kann.
Zum Beispiel verwenden wir einen TI F28335 DSP; Einige Bereiche des RAM sind 0-Wartezustand für Programm- und Datenspeicher. Wenn Sie also Code im RAM ausführen, wird dieser mit 1 Zyklus pro Befehl ausgeführt (mit Ausnahme der Befehle, die mehr als 1 Zyklus benötigen). Wenn Sie Code aus dem FLASH-Speicher ausführen (mehr oder weniger integriertes EEPROM), kann er jedoch nicht mit den vollen 150 MHz ausgeführt werden und ist um ein Vielfaches langsamer.
In Bezug auf Hochgeschwindigkeits-Interrupt-Code müssen Sie eine Reihe von Dingen lernen.
Machen Sie sich zunächst mit Ihrem Compiler vertraut. Wenn der Compiler gute Arbeit leistet, sollte er für die meisten Dinge nicht viel langsamer sein als die handcodierte Assembly. (wo "so viel langsamer": ein Faktor von 2 wäre für mich in Ordnung; ein Faktor von 10 wäre inakzeptabel) Sie müssen lernen, wie (und wann) Compiler-Optimierungsflags verwendet werden, und von Zeit zu Zeit sollten Sie nachsehen am Ausgang des Compilers, um zu sehen, wie es funktioniert.
Einige andere Dinge, die der Compiler tun kann, um den Code zu beschleunigen:
Verwenden Sie Inline-Funktionen (ich kann mich nicht erinnern, ob C dies unterstützt oder ob es sich nur um einen C ++ - Ismus handelt), sowohl für kleine Funktionen als auch für Funktionen, die nur ein- oder zweimal ausgeführt werden sollen. Der Nachteil ist, dass Inline-Funktionen schwer zu debuggen sind, insbesondere wenn die Compiler-Optimierung aktiviert ist. Sie ersparen Ihnen jedoch unnötige Aufruf- / Rückgabesequenzen, insbesondere wenn die "Funktions" -Abstraktion eher für konzeptionelle Entwurfszwecke als für die Codeimplementierung dient.
Sehen Sie im Handbuch Ihres Compilers nach, ob es über intrinsische Funktionen verfügt. Hierbei handelt es sich um compilerabhängige integrierte Funktionen, die direkt den Montageanweisungen des Prozessors zugeordnet sind. Einige Prozessoren verfügen über Montageanweisungen, die nützliche Funktionen wie Min / Max / Bit-Umkehrung ausführen. Auf diese Weise können Sie Zeit sparen.
Wenn Sie numerische Berechnungen durchführen, stellen Sie sicher, dass Sie die Funktionen der Mathematikbibliothek nicht unnötig aufrufen. Wir hatten einen Fall, in dem der Code so etwas wie
y = (y+1) % 4
ein Zähler mit einer Periode von 4 war, und erwarteten, dass der Compiler das Modulo 4 als bitweises UND implementiert. Stattdessen wurde die Mathematikbibliothek aufgerufen. Also haben wir ersetzt, umy = (y+1) & 3
zu tun, was wir wollten.Machen Sie sich mit der Seite der Bit-Twiddling-Hacks vertraut . Ich garantiere Ihnen, dass Sie mindestens eine davon häufig verwenden werden.
Sie sollten auch die Timer-Peripheriegeräte Ihrer CPU verwenden, um die Codeausführungszeit zu messen. Die meisten von ihnen verfügen über einen Timer / Zähler, der so eingestellt werden kann, dass er mit der CPU-Taktfrequenz ausgeführt wird. Erfassen Sie eine Kopie des Zählers am Anfang und Ende Ihres kritischen Codes, und Sie können sehen, wie lange es dauert. Wenn Sie dies nicht tun können, besteht eine andere Alternative darin, einen Ausgangspin am Anfang Ihres Codes abzusenken und am Ende anzuheben und diesen Ausgang auf einem Oszilloskop zu betrachten, um die Ausführung zeitlich zu steuern. Jeder Ansatz hat Kompromisse: Der interne Timer / Zähler ist flexibler (Sie können mehrere Dinge zeitlich festlegen), aber es ist schwieriger, die Informationen herauszuholen, während das Setzen / Löschen eines Ausgangsstifts auf einem Bereich sofort sichtbar ist und Sie Statistiken erfassen können Es ist schwierig, mehrere Ereignisse zu unterscheiden.
Schließlich gibt es eine sehr wichtige Fähigkeit, die mit Erfahrung verbunden ist - sowohl allgemein als auch mit bestimmten Prozessor / Compiler-Kombinationen: zu wissen, wann und wann nicht zu optimieren ist . Im Allgemeinen lautet die Antwort nicht optimieren. Das Donald Knuth-Zitat wird häufig auf StackOverflow veröffentlicht (normalerweise nur der letzte Teil):
Aber Sie befinden sich in einer Situation, in der Sie wissen, dass Sie eine Art Optimierung durchführen müssen. Es ist also an der Zeit, in die Kugel zu beißen und zu optimieren (oder einen schnelleren Prozessor oder beides zu bekommen). Schreiben Sie NICHT Ihren gesamten ISR in Assembly. Das ist fast eine garantierte Katastrophe - wenn Sie dies tun, werden Sie innerhalb von Monaten oder sogar Wochen Teile dessen vergessen, was Sie getan haben und warum, und der Code ist wahrscheinlich sehr spröde und schwer zu ändern. Es gibt jedoch wahrscheinlich Teile Ihres Codes, die sich gut für die Montage eignen .
Anzeichen dafür, dass Teile Ihres Codes für die Baugruppencodierung gut geeignet sind:
Lernen Sie die Funktionsaufrufkonventionen Ihres Compilers kennen (z. B. wo die Argumente in Registern abgelegt werden und welche Register gespeichert / wiederhergestellt werden), damit Sie C-aufrufbare Assembly-Routinen schreiben können.
In meinem aktuellen Projekt haben wir eine ziemlich große Codebasis mit kritischem Code, die in einem 10-kHz-Interrupt ausgeführt werden muss (100 usec - klingt vertraut?), Und es gibt nicht so viele Funktionen, die in Assembly geschrieben werden. Dies sind Dinge wie CRC-Berechnung, Software-Warteschlangen, ADC-Verstärkung / Offset-Kompensation.
Viel Glück!
quelle
Eine andere Sache zu beachten - es gibt wahrscheinlich einige Optimierungen, die Sie durchführen können, um Ihren Code effizienter zu machen.
Zum Beispiel - Ich habe eine Routine, die innerhalb eines Timer-Interrupts ausgeführt wird. Die Routine muss innerhalb von 52 µS abgeschlossen sein und dabei eine große Menge an Speicher durchlaufen.
Ich habe eine große Geschwindigkeitssteigerung erreicht, indem ich die Hauptzählervariable an ein Register mit (auf meinem µC & Compiler - anders für Sie) gesperrt habe:
Ich kenne das Format für Ihren Compiler nicht - RTFM, aber Sie können etwas tun, um Ihre Routine zu beschleunigen, ohne zur Assembly wechseln zu müssen.
Trotzdem können Sie Ihre Routine wahrscheinlich viel besser optimieren als der Compiler. Wenn Sie also zur Assembly wechseln, können Sie möglicherweise einige massive Geschwindigkeitssteigerungen erzielen.
quelle