Können schnellere Prozessoren / Uhren mehr Code ausführen?

9

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?

jwbensley
quelle
.... Ein ATmega328 ist KEIN ARM-Chip. Es ist ein AVR.
Vicatcu
Prost, korrigiert!
Jwbensley

Antworten:

9

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.).

vicatcu
quelle
Hallo, danke für deine informative Antwort! Ich habe eine davon gesehen ( coolcomponents.co.uk/catalog/product_info.php?products_id=808 ). Sie sagten, ein AVR kann nicht schneller als 20 MHz sein. Warum ist das so? Der Chip auf der obigen Platine ( uk.farnell.com/stmicroelectronics/stm32f103rbt6/… ) ist ein 72-MHz-ARM. Kann ich davon eine angemessene Leistungssteigerung erwarten, wie ich es oben beschrieben habe?
Jwbensley
2
Durch Verdoppeln der Verarbeitungsgeschwindigkeit wird der Befehlsdurchsatz möglicherweise nicht erhöht, da Sie möglicherweise die Geschwindigkeit überschreiten, mit der Befehle aus dem Flash abgerufen werden können. An diesem Punkt treffen Sie auf "Flash-Wartezustände", in denen die CPU pausiert, während sie darauf wartet, dass die Anweisung vom Flash kommt. Einige Mikrocontroller umgehen dies, indem Sie Code aus dem RAM ausführen können, der viel schneller als FLASH ist.
Majenko
@Majenko: lustig, wir haben beide zur gleichen Zeit den gleichen Punkt gemacht.
Jason S
Es passiert ... deins ist besser als meins :)
Majenko
1
OK, ich habe Vicatcus Antwort als "die Antwort" markiert. Ich denke, es war am besten geeignet in Bezug auf meine ursprüngliche Frage nach der Geschwindigkeit in Bezug auf die Leistung, obwohl alle Antworten großartig sind und ich mit allen Antworten wirklich zufrieden bin. Sie haben mir gezeigt, dass es ein umfassenderes Thema ist, als ich zuerst erkannt habe, und so lehren sie mich alle viel und geben mir viel zu forschen, also danke an alle: D
jwbensley
8

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) % 4ein 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, um y = (y+1) & 3zu 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):

Wir sollten kleine Wirkungsgrade vergessen, etwa in 97% der Fälle: Vorzeitige Optimierung ist die Wurzel allen Übels

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:

  • Funktionen, die gut enthalten sind, gut definierte kleine Routinen, die sich wahrscheinlich nicht ändern
  • Funktionen, die bestimmte Montageanweisungen verwenden können (Min / Max / Rechtsverschiebung / usw.)
  • Funktionen, die viele Male aufgerufen werden (Sie erhalten einen Multiplikator: Wenn Sie bei jedem Aufruf 0,5 usec speichern und 10 Mal aufgerufen werden, sparen Sie 5 usec, was in Ihrem Fall von Bedeutung ist).

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!

Jason S.
quelle
gute Ratschläge zu empirischen Techniken zur Messung der Ausführungszeit
vicatcu
Eine weitere großartige Antwort auf meine Frage, vielen Dank Jason S für dieses großartige Stück Wissen! Zwei Dinge, die nach dem Lesen offensichtlich sind; Erstens kann ich den Interrupt von jeweils 100uS auf 500uS erhöhen, um dem Code mehr Zeit für die Ausführung zu geben. Mir ist jetzt klar, dass es mir nicht wirklich zugute kommt, so schnell zu sein. Zweitens denke ich, dass mein Code vielleicht zu ineffizient ist, mit der längeren Unterbrechungszeit und dem besseren Code könnte alles in Ordnung sein. Stackoverflow ist ein besserer Ort, um den Code zu posten, also werde ich ihn dort posten und hier einen Link dazu setzen. Wenn jemand einen Blick darauf werfen und Empfehlungen
abgeben
5

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:

register unsigned int pointer asm("W9");

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.

Majenko
quelle
lol Ich habe "gleichzeitig" meine eigene Antwort über Assembler-Tuning und Registerzuordnung kommentiert :)
vicatcu
Wenn es 100us auf einem 16-MHz-Prozessor braucht, ist es offensichtlich ziemlich groß, also ist das eine Menge Code, den man optimieren muss. Ich habe gehört, dass heutige Compiler etwa das 1,1-fache des Codes produzieren als handoptimierte Assemblys. Für eine so große Routine absolut nicht wert. Für 20% Rabatt auf eine 6-Zeilen-Funktion, vielleicht ...
DefenestrationDay
1
Nicht unbedingt ... Es können nur 5 Codezeilen in einer Schleife sein. Und es geht nicht um Code Größe , sondern um Codeeffizienz . Möglicherweise können Sie den Code anders schreiben, damit er schneller ausgeführt wird. Ich weiß für meine Interrupt-Routine, die ich getan habe. Zum Beispiel Größe für Geschwindigkeit opfern. Indem Sie denselben Code zehnmal hintereinander ausführen, sparen Sie Zeit für die Ausführung der Schleife und der zugehörigen Zählervariablen. Ja, der Code ist zehnmal länger, läuft aber schneller.
Majenko
Hallo Majenko, ich kenne keine Montage, aber ich hatte darüber nachgedacht, sie zu lernen, und dachte, dass der Arduino weniger kompliziert sein wird als mein Desktop-Computer, so dass dies ein guter Zeitpunkt zum Lernen sein könnte, insbesondere, wie ich wissen möchte mehr darüber, was los ist und eine niedrigere Ebene. Wie andere gesagt haben, würde ich das Ganze nicht nur bestimmte Teile neu schreiben. Mein Verständnis ist, dass ich innerhalb von C in ASM ein- und aussteigen kann. Ist das richtig? Kann man auf diese Weise diese Mischung aus C und ASM erreichen? Ich werde auf Stackoverflow für die Details posten, kurz nach einer allgemeinen Idee.
Jwbensley
@ Javano: Ja. Sie können ASM in C ein- und aussteigen lassen. Viele eingebettete Systeme wurden so geschrieben - in einer Mischung aus C und Assembly - hauptsächlich, weil es einige Dinge gab, die in den primitiven C-Compilern, die auf der Website verfügbar sind, einfach nicht möglich waren Zeit. Moderne C-Compiler wie gcc (der von Arduino verwendete Compiler) verarbeiten jetzt jedoch die meisten und in vielen Fällen alle Dinge, die früher Assemblersprache erforderten.
Davidcary