Hallo, ich nutze gcc für die (Arduino) AVR Plattform und habe eine ISR die 72000 mal pro Sekunde aufgerufen wird (IRSND) und nur ein paar Register braucht (4 Pushs). Alle 4 mal wird eine fette Funktion aufgerufen, was dazu führt, dass vor der ISR jetzt 16 Pushs stehen. Wie bekomme ich den Compiler überredet, die 12 (oder auch mehr) Pushs vor die Funktion und nicht vor die ISR zu setzen. Ein attributieren der Funktion mit __attribute__((noinline)) hat leider nur genau ein Push von der ISR zur Funktion verschoben :-(. Das Ganze braucht jetzt 50% CPU bei 16 MHz, ist also noch hinnehmbar, aber schön ist anders.
Das verstehe ich nicht ganz. Warum willst du inlining verhindern? Gerade deswegen werden diese ganzen Pushs doch gemacht. Da die ISR mangels inlining nicht weiß, welche Register in der Funktion benötigt werden, muss sie alle auf den Stack werfen.
@ Armin @ Rolf Wenn ich das richtig verstehe, scheint es mir, als war die Vermutung des TO, dass inline dafür sorgt, dass die pushes schon am Anfang der ISR erfolgten. Unerwartet für den TO hat noinline nicht die genau gegenteilige Wirkung. Der gcc scheint die Tatsache, dass die Funktion nur jedes vierte Mal aufgerufen wird, nicht zu berücksichtigen. Die Frage wäre, ob er sich das überhaupt ausklamüsern könnte; vermutlich ja. Manchmal tut er es aber in "offensichtlichen" Fällen nicht. Und die nächste Frage, ob gcc so implementiert ist, dass er das tut; offenbar nicht. Leider kann ich die letzte Frage nicht beantworten. Aber hier treibt sich doch einer herum :-) der am gcc zugange ist. Interessant wäre mal der Code um zu sehen, ob man wenigsten die erste Frage eindeutig beantworten kann.
Wenn ich so darüber nachdenke, dann sind 50% CPU-Zeit in der ISR und die Tatsache, dass eine "lange" Funktion in der ISR aufgerufen wird, vielleicht auch ein Zeichen, dass man am Design noch was ändern könnte. Ist aber vielleicht auch unumgänglich. Aber ohne Code natürlich schwer zu sagen.
Wie lang dauert denn diese Mega-Funktion und gehen in der Zeit Interrupts verloren?
Ja danke für Euer Verständnis. Hier https://github.com/ukw100/IRMP/blob/9555f910af007838a7638c98684192a54cd0ff70/src/irsnd.c.h#L3336 gibts den Code. Ich möchte gcc dazu überreden, 4 Pushs zu machen, den ISR Code auszuführen und jedes 4. mal eben einen Call von irsnd_ISR(). Das Call Ziel (die Funktion irsnd_ISR()) soll dann die restlichen 12 oder wieviel auch immer Pushes machen. Da die Funktion nur von der ISR aufgerufen wird und -Os aktiv ist, wird sie automatisch geinlined, was ja prinzipiell richtig ist, aber eben viel Zeit für Pushs in 3 von 4 Fällen kostet. Wenn ich das noinline attribut setze, spare ich nur einen push, genauso, wie wenn ich die Funktion dummymäßig noch von woanders aufrufe.
Und es interessiert mich eher akademisch. Mit 50% CPU während des Sendens eines IR Frames (120ms), kommt man schon sehr weit, da besteht kein Leidensdruck. Deshalb nochmal danke für Euer Bemühen.
Ist es denn für den Compiler überhaupt möglich, zur Compiletime festzustellen, dass die Funktion nur jedes 4. Mal aufgerufen wird? Scheint mir schwierig. Wie auch immer: du könntest die Funktion mit attribute(signal) deklarieren (dann mach sie die nötigen pushs selbst) und den Aufruf im Interrupt-Handler vor dem Compiler irgend verstecken (z.B. asm("call foo")), so dass sie nur die für sie selbst notwendigen pushs macht (einige pushs sind dann doppelt). Sieht mir aber nach ziemlich fragilem hack aus ... kann man mal machen, um schnell festzustellen, ob das genügend Zeit einspart, aber nix für Dauer.
foobar schrieb: > Ist es denn für den Compiler überhaupt möglich, zur Compiletime > festzustellen, dass die Funktion nur jedes 4. Mal aufgerufen wird? > Scheint mir schwierig. Muss er ja gar nicht. Er müsste ja prinzipiell nur die PUSHs, die für den Aufruf nötig sind, mit in das if reinziehen. Dann werden sie immer nur dann gemacht, wenn die Funktion auch aufgerufen wird, ganz egal wie oft das passiert. Soweit ich weiß, macht der Compiler sowas aber immer gesammelt am Anfang der aufrufenden Funktion.
Man könnte die lange Funktion in den SPM READY Interrupt legen. Die kurze Funktion gibt jeden 4. Lauf diesen Interrupt frei und der sperrt sich wieder. Falls es sich um einen Timerinterrupt handelt, kann man aber auch 2 Interrupts des Timers nehmen (COMPA, COMPB).
Peter D. schrieb: > Falls es sich um einen Timerinterrupt handelt, kann man aber auch 2 > Interrupts des Timers nehmen (COMPA, COMPB). Danke, gute Idee, geht aber hier nicht, da der 2. Vektor schon von der Arduino tone() library belegt ist. Außerdem wollte ich ja eigentlich den Compiler überreden... foobar schrieb: > Wie auch immer: du könntest die Funktion mit attribute(signal) > deklarieren Auch sehr gute Idee, aber
1 | error: 'signal' function cannot return a value |
Und wenn ich die Funktion zu void ändere, hab ich 1 Push gespart. Kann ich jetzt aber mit Assembler heimlich die Funktion aufrufen? Das müsste dann klappen :-) Ich glaubs ja selber nicht, aber wenn ich nur den Call auskommentiere, bekomme ich wirklich nur meine 4 Pushs.: b3a: 1f 92 push r1 b3c: 0f 92 push r0 b3e: 0f b6 in r0, 0x3f ; 63 b40: 0f 92 push r0 b42: 11 24 eor r1, r1 b44: 8f 93 push r24 b46: 80 91 16 01 lds r24, 0x0116 ; 0x800116 <__data_end>
:
Bearbeitet durch User
@ Armin Naja. Ist das wirklich den Aufwand wert? Wegen 12 Pushes/Pops, also 24 Takten? Oder sollte man die Kröte schlucken? --- Aber mal ein anderer Vorschlag: Die ganze Zuweisungs/orgie/ (Scherz :-) ) in der Funktion, in switch (irsnd_protocol), kann man vermutlich mit einem Vektor von Strukturen etwas effizienter gestalten, denke ich. Anstatt soviele (ich schätze zwischen 5 und 8) Variablen zu setzen, könnte man auch einen Zeiger oder Index benutzen. Letztlich muss ja später noch einmal auf diese Variablen zugegriffen werden. Aber soviele Register hat z.B. der AVR gar nicht um das alles bis dahin zu speichern. Möglicherweise (so ganz im Detail habe ich das auch nicht analysiert) brächte das eine kürzere Laufzeit (und kürzeren Code), weil die Variablen nicht einzeln zweimal angefasst werden müssen. Im Grunde ist das m.M.n. genau das oben schon beschriebene Problem der Codeanalyse. Der Compiler wird die Zuweisungen nicht aufschieben, bis es soweit ist, weil er statisch nicht weiß (nicht wissen will :-) ) welcher der Fälle (switch-case) letztlich zutrifft. Vielleicht magst Du Dir das ja mal überlegen.
Armin J. schrieb: > Ich möchte gcc dazu überreden, 4 Pushs zu machen, den ISR Code > auszuführen und jedes 4. mal eben einen Call von irsnd_ISR(). Das Call > Ziel (die Funktion irsnd_ISR()) soll dann die restlichen 12 oder wieviel > auch immer Pushes machen. Geht nicht. Du kannst entweder irsnd_ISR per always_inline in die ISR holen, was dann allerdings dazu führt, daß immer noch bei jedem Aufruf alle dafür benötigten Register gesichert werden, oder du kannst irsnd_ISR naked machen, und die push/pop-Orgie zu Anfang und Ende manuell einfügen. Ist dann halt eine üble Felerquelle bei Code- oder Compileränderungen. Oder auch beides kombiniert, je nachdem, wie groß der Leidensdruck zur Optimierung ist. Wenns ganz eng wird, schreib die ganze ISR in Assembler. Oliver
:
Bearbeitet durch User
Theor schrieb: > Naja. Ist das wirklich den Aufwand wert? Wegen 12 Pushes/Pops, also 24 > Takten? Oder sollte man die Kröte schlucken? Es sind jeweils zwei Taktzyklen, als insgesamt 48. Macht immerhin fast 3,5 Millionen Taktzyklen, die pro Sekunde nur dafür aufgewendet werden.
Rolf M. schrieb: > Theor schrieb: >> Naja. Ist das wirklich den Aufwand wert? Wegen 12 Pushes/Pops, also 24 >> Takten? Oder sollte man die Kröte schlucken? > > Es sind jeweils zwei Taktzyklen, als insgesamt 48. Macht immerhin fast > 3,5 Millionen Taktzyklen, die pro Sekunde nur dafür aufgewendet werden. Danke für die Korrektur, Rolf. Ja. Es sind 2 Zyklen jeweils pro PUSH und POP, nicht nur einer.
Moin, Wenns auf so Zeugs ankommt, wuerd' ich dann doch mal langsam auf die dafuer geeignetere Sprache gehen. Muss ja nicht gleich alles in Asm sein, aber halt das Interruptgedoens, wo's auf jeden Takt ankommt. Denn selbst wenn man mit irgendwelchen wahnsinnigen Verrenkungen einen gcc mal dazu bringt, das so zu machen, wie man's grad' gerne haette, dann ist doch die Wahrscheinlichkeit gross, dass eine klitzekleine Aenderung der toolchain alles wieder auf den Kopf stellt. Gruss WK
Dergute W. schrieb: > Wenns auf so Zeugs ankommt, wuerd' ich dann doch mal langsam auf die > dafuer geeignetere Sprache gehen. Muss ja nicht gleich alles in Asm > sein, aber halt das Interruptgedoens, wo's auf jeden Takt ankommt. Es könnte schon helfen, eher auf die nicht unbedingt für ihre hohe Effizienz bekannten Arduino-Funktionen in der ISR zu verzichten. Und man könnte schauen, ob das wirklich alles in der ISR ausgeführt werden muss. Generell würde ich auf einem AVR versuchen, Funktionsaufrufe in ISRs zu vermeiden.
:
Bearbeitet durch User
Hallo, danke für die Tips zum Arduino/ C Programmieren. Ich bin da jedoch der falsche Ansprechpartner. Das Ganze stammt von dem hier bekannten Frank M. (ukw)(Moderator) und ist die IRSND Library. Ich arbeite das Ganze nur auf und mache es der Arduino Community zugänglich. Als Zusammenfassung lässt sich wohl sagen: *Der Compiler kanns nicht und man kann ihn auch nicht mit Tricks dazu bringen*. Ist nicht schlimm, nur einen Versuch die Cracks zu fragen, war es wert. Dann nochmal Danke und bleibt gesund. Armin
Armin J. schrieb: > Wie bekomme ich den Compiler überredet, die 12 (oder auch mehr) Pushs > vor die Funktion und nicht vor die ISR zu setzen. Dazu musst du Shrink-Wrapping im AVR-Backend implementieren. Diese Optimierung gibt es bislang nicht bzw. entsprechende Hooks sind für avr nicht implementiert, d.h. wenn du wirklich darauf angewiesen bist, dann bleibt dir nur Assembler (oder C++ für die GCC-Quellen).
:
Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.