Forum: Compiler & IDEs In ISR Inlining einer selten benutzten Funktion vermeiden um pushs zu sparen


von Armin J. (arminj)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Theor (Gast)


Lesenswert?

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

von Theor (Gast)


Lesenswert?

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.

von Ingo Less (Gast)


Lesenswert?

Wie lang dauert denn diese Mega-Funktion und gehen in der Zeit 
Interrupts verloren?

von Armin J. (arminj)


Lesenswert?

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.

von Armin J. (arminj)


Lesenswert?

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.

von foobar (Gast)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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

von Armin J. (arminj)


Lesenswert?

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
von Theor (Gast)


Lesenswert?

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

von Oliver S. (oliverso)


Lesenswert?

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
von Rolf M. (rmagnus)


Lesenswert?

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.

von Theor (Gast)


Lesenswert?

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.

von Dergute W. (derguteweka)


Lesenswert?

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

von Rolf M. (rmagnus)


Lesenswert?

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
von Armin J. (arminj)


Lesenswert?

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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
Noch kein Account? Hier anmelden.