Hi, ich habe ein Programm, mit einer ISR, welche sehr schnell sein soll. Daher ist diese in Assembler implementiert. In der ISR entscheidet sich, ob Funktion A oder Funktion B aufgerufen werden muss. Funktion A ist in Assembler geschrieben, Funktion B in "C". Prinzipiell funktioniert das auch. Die ISR hat das Attribut "naked", um sich das (langsame) Sichern der Register zu sparen. Benötigt werden nur 2 Stück, das ist per Hand implementiert. In 99% der Fälle wird auch nur Funktion A aufgerufen (kommt mit 2 Registern aus), in 1% der Fälle Funktion B, welche eben diese Register alle benötigt. Wenn ich jetzt aber einen CALL auf die Funktion B mache, dann geht diese davon aus, dass die Register bereits gesichert wurden und überschreibt sie einfach. Das ist natürlich nicht gut. Gibt es eine Möglichkeit der Funktion B bzw. dem Compiler zu sagen, dass die Register, welche dort verwendet werden, nochmal gesichert werden müssen?
hatte vergessen zu erwähnen: ich verwende den avr-gcc aus dem aktuellen atmel studio
asmprogger schrieb: > Gibt es eine Möglichkeit der Funktion B bzw. dem Compiler zu sagen, dass > die Register, welche dort verwendet werden, nochmal gesichert werden > müssen? Den Compiler interessiert das nicht, dem hast du ja vorher extra verboten, sich da reinzuhängen. Das musst du schon selbermachen. Evtl. per Inline-Assembler?
asmprogger schrieb: > ich habe ein Programm, mit einer ISR, welche sehr schnell sein soll. > Daher ist diese in Assembler implementiert. Non sequitur.
asmprogger schrieb: > Gibt es eine Möglichkeit der Funktion B bzw. dem Compiler zu sagen, dass > die Register, welche dort verwendet werden, nochmal gesichert werden > müssen? Du könntest der Funktion das Attribut "signal" verpassen, dann wird es vom Compiler als "ISR-Funktion" betrachtet, was das Sichern aller verwendeten Register einschließt. Zwei Dinge gibt es aber zu beachten: 1) Es entsteht etwas zusätzlicher (unnötiger) Overhead. 2) Die Funktion schließt mit einem RETI ab. Gegen 1 kann man nichts machen (IMO). Die Auswirkungen von 2 (vorzeitiges Enablen der Interrupts) kann man eliminieren, indem man dem Call der Funktion ein sofortiges cli folgen lässt.
Ralf G. schrieb: > Stefan Rand schrieb: >> Non sequitur. > > Um etwas genauer zu werden: Funktionsaufrufe in einer ISR :-/ Das ist nicht genauer. Mir gehts schlicht darum, daß Assembler nicht aus "muß schnell sein" folgt.
Stefan Rand schrieb: > Mir gehts schlicht darum, daß Assembler nicht aus "muß schnell sein" > folgt. Das stimmt. Der häufiger vorkommende Fehler ist ist allerdings, alles in die ISR zu packen um sich dann zu wundern, dass die Zeit nicht reicht. Und Funktionsaufrufe verschlimmbessern die Situation nur.
Stefan Ernst schrieb: > asmprogger schrieb: >> Gibt es eine Möglichkeit der Funktion B bzw. dem Compiler zu sagen, dass >> die Register, welche dort verwendet werden, nochmal gesichert werden >> müssen? > > Du könntest der Funktion das Attribut "signal" verpassen, dann wird es > vom Compiler als "ISR-Funktion" betrachtet, was das Sichern aller > verwendeten Register einschließt. Zwei Dinge gibt es aber zu beachten: > 1) Es entsteht etwas zusätzlicher (unnötiger) Overhead. > 2) Die Funktion schließt mit einem RETI ab. > > Gegen 1 kann man nichts machen (IMO). Die Auswirkungen von 2 > (vorzeitiges Enablen der Interrupts) kann man eliminieren, indem man dem > Call der Funktion ein sofortiges cli folgen lässt. Auf ATxmega werden ISRs direkt nach dem RETI aktiviert, es bleibt keine Zeit für CLI wie bei den "alten" Cores. Lösen kann man das, indem man folgenden Hack anwendet:
1 | __asm (".macro reti\n" |
2 | "ret\n" |
3 | ".endm"); |
vor der ISR-Funktion (ich würd ISR einem Attribute signal vorziehen) und übersetzen mit -fno-toplevel-reorder. Zudem darf nach dem asm keine Funktion ausser der ISR-Funktion folgen. Alternativ kann man um den Aufruf der Funktion auf asm-Ebene alle call-clobbered Register sichern und wieder herstellen. Dies sind: SREG, R0-R1, R18-R27, R30-R31. Zudem muß R1 aka. __zero_reg__ mit 0 initialisiert werden.
Ok, danke soweit für die Antworten. :-) Da scheints wohl keinen sehr schönen/angenehmen Weg dafür zu geben. Ich werde dann die Register "per Hand" sichern. Sehr interessant finde ichs aber, wie manche Leute sich anmaßen den Code zu kommentieren, wo sie ihn weder gesehen haben, noch überhaupt wissen, was er tun soll. Darüber, dass sich er GCC unsinnigerweiße erstmal etliche Register sichert meckert keiner, aber WEHE man ruft eine Funktion auf, das macht die Sache ja immer gleich unmöglich langsam. So ein Schmarrn...
asmprogger schrieb: > Darüber, dass sich er GCC unsinnigerweiße erstmal > etliche Register sichert meckert keiner, Das ist nicht unsinnig!!
asmprogger schrieb: > Darüber, dass sich er GCC unsinnigerweiße erstmal > etliche Register sichert meckert keiner, Aber hallo, darüber wird natürlich auch oft und gerne gemeckert! Bislang war das "Problem" allerdings noch keinem wichtig genug, eine Lösung dafür zu erarbeiten...
Ralf G. schrieb: > asmprogger schrieb: >> Darüber, dass sich er GCC unsinnigerweiße erstmal >> etliche Register sichert meckert keiner, > > Das ist nicht unsinnig!! Doch, ist es. Auf jeden Fall sollte das Sichern der Register hinter das if() verschoben werden und nicht im Prolog der ISR stattfinden. Insofern ist die konsequente Lösung, die ISR als naked zu deklarieren und die Register vor dem Anspringen der C-Funktion händisch zu sichern. Was mich persönlich viel mehr nervt (weil es IMHO sehr einfach zu optimieren wäre) ist daß r1 aka _zero_reg_ immer erst auf den Stack gesichert und dann mit 0 initialisiert wird, auch dann wenn es nirgendwo in der ISR gebraucht wird. @asmprogger: http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_reg_usage XL
Axel Schwenke schrieb: > Doch, ist es. Auf jeden Fall sollte das Sichern der Register hinter das > if() verschoben werden und nicht im Prolog der ISR stattfinden. Insofern > ist die konsequente Lösung, die ISR als naked zu deklarieren und die > Register vor dem Anspringen der C-Funktion händisch zu sichern. Und bei mehreren 'IFs'? Jedesmal? Nur für eine ISR, die mehrere Funktionen aufrufen will, die aus irgendwelchen Dateien zusammengesucht werden? (Gerne auch in Assembler!) Wer an dem Punkt ankommt, hat was anderes falsch gemacht.
Axel Schwenke schrieb: > Was mich persönlich viel mehr nervt (weil es IMHO sehr einfach zu > optimieren wäre) ist daß r1 aka _zero_reg_ immer erst auf den Stack > gesichert und dann mit 0 initialisiert wird, auch dann wenn es nirgendwo > in der ISR gebraucht wird. Im Gegenteil, das ist ganz und garnicht einfach zu optimieren. Daß klar ist, was das Resultat der Optimierung sein soll, haißt noch lange nicht, daß es einfach zu implementieren ist. I.W. bedeutet es, fast das ganze avr-Backend des GCC neu zu schreiben. Ich selbst bin nie an diese Optimierung (PR20296) rangegangen: Praktisch das ganze avr-Backend umkrempeln, um im Endeffekt maximal 3 Instruktionen zu sparen. Wenn es sooo zeitkritisch ist, dann führt an Assembler eh kein Weg vorbei. Ohnehin wird das 0-Register bei größeren Funktionen fast immer gebraucht, d.h. die Optimierung würde nur bei Mini-Funktionen was bringen. Aber es steht jedem frei, das anders zu sehen und in medias res zu gehen, d.h. in die GCC-Quellen und ein Patch zu erarbeiten :-) Pfiffige Entwickler gibt's hier doch genug; hier sitzt man an der Quelle -- sollte man eigentlich meinen...
:
Bearbeitet durch User
Ralf G. schrieb: > Axel Schwenke schrieb: >> Doch, ist es. Auf jeden Fall sollte das Sichern der Register hinter das >> if() verschoben werden und nicht im Prolog der ISR stattfinden. Insofern >> ist die konsequente Lösung, die ISR als naked zu deklarieren und die >> Register vor dem Anspringen der C-Funktion händisch zu sichern. > > Und bei mehreren 'IFs'? Jedesmal? Nur für eine ISR, die mehrere > Funktionen aufrufen will, die aus irgendwelchen Dateien zusammengesucht > werden? Wo soll das Problem sein? Für eine ISR ohne Funktionsaufrufe kann gcc ja jetzt schon feststellen, welche Register er in der ISR benutzt und welche nicht. Jetzt müßte er bloß noch den PUSH-opcode möglichst nahe an die tatsächliche Verwendung des Registers schieben. Und wenn er beim Aufruf einer Funktion mit C linkage davon ausgeht, daß die alle call-saved Register clobbered, dann kann er die ja meinetwegen unmittelbar vor dem Funktionsaufruf auf den Stack schieben. Nur eben nicht im Prolog der ISR. Tatsächlich würde ich mir ein Attribut ähnlich signal wünschen, das den Compiler veranlaßt, einer Funktion Prolog und Epilog zum Sichern aller benutzten Register hinzuzufügen (aber mit RET statt RETI zurückzukehren). Dann könnte man solche Funktionen "blind" aufrufen. XL
Johann L. schrieb: > Im Gegenteil, das ist ganz und garnicht einfach zu optimieren. Daß klar > ist, was das Resultat der Optimierung sein soll, haißt noch lange nicht, > daß es einfach zu implementieren ist. > > I.W. bedeutet es, fast das ganze avr-Backend des GCC neu zu schreiben. OK, dann war meine Opinion wohl doch zu Humble ;) In der Tat spreche ich von winz-ISR (nur dort fällt die "überflüssige" Sicherung von R1 ins Gewicht). So etwas wie
1 | volatile uint8_t t0u; |
2 | |
3 | ISR (TIMER0_OVF_vect) |
4 | {
|
5 | ++t0u; |
6 | }
|
das ist ein sehr typisches Konstrukt in meinen Programmen. Noch öfter als Zähler, der bis 0 runterzählt und dann da bleibt:
1 | volatile uint8_t countdown; |
2 | ...
|
3 | if ((uint8_t r=countdown)) |
4 | countdown= r-1; |
Dann bleibt wohl nur die Variante, das in (Inline) ASM zu schreiben. Nicht portabel, aber irgendwas ist ja immer. XL
asmprogger schrieb: > Darüber, dass sich er GCC unsinnigerweiße erstmal etliche Register > sichert meckert keiner, aber WEHE man ruft eine Funktion auf, das macht > die Sache ja immer gleich unmöglich langsam. Da beteht ein direkter Zusammenhang: GCC merkt sich die ganzen Register gerade weil eine Funktion aufgerufen wird, denn er weiß nicht, welche Register die Funktion braucht. Axel Schwenke schrieb: > Wo soll das Problem sein? Für eine ISR ohne Funktionsaufrufe kann gcc ja > jetzt schon feststellen, welche Register er in der ISR benutzt und > welche nicht. Jetzt müßte er bloß noch den PUSH-opcode möglichst nahe an > die tatsächliche Verwendung des Registers schieben. Bei einer ISR, die mit mehreren ifs arbeitet und in manchen davon einen Funktionsaufruf hat, würde das zu einer Codeexplosion führen, weil dann die PUSH-Orgie mehrmals eingefügt werden müßte. Und das, um manchmal weniger Taktzyklen in der ISR zu brauchen? In der Regel zählt bei ISRs eh nur der Worst Case. Entweder eine ISR ist immer schnell genug, oder sie ist es nicht. Eine, die manachmal, aber eben nicht immer besonders schnell ist, bringt keinen besonderen Vorteil. > Tatsächlich würde ich mir ein Attribut ähnlich signal wünschen, das den > Compiler veranlaßt, einer Funktion Prolog und Epilog zum Sichern aller > benutzten Register hinzuzufügen (aber mit RET statt RETI > zurückzukehren). Dann könnte man solche Funktionen "blind" aufrufen. Wenn eine Funktion aber ausschließlich aus einer ISR heraus aufgerufen wird, ist es in der Regel nicht unbedingt nötig, den Code in eine separate Funktion zu packen. Das produziert dann nur noch zusätzlichen Funktionsaufrufs-Overhead. Und wenn eine Funktion, wie du sie beschreibst, auch außerhalb von ISRs aufgerufen wird, hätte man dort immer den Overhead der Registersicherungen.
asmprogger schrieb: > Da scheints wohl keinen sehr > schönen/angenehmen Weg dafür zu geben. Ich werde dann die Register "per > Hand" sichern. Was ist daran unschön? Setzt halt vor und hinter den den Aufruf der C-Funktion noch ein paar Zeilen Assembler, die die caller-saved Register pushen und poppen, das wars. So schlimm ist das nun auch wieder nicht. Unschön wird es erst, wenn du auch da um jeden Zyklus feilschen willst, und nur genau die Register retten willst, die auch tatsächlich benutzt werden. Dann darfst du das nach jeder Änderung am C-Code anpassen, mit durchaus realem Risiko, es zu vergessen. Oliver
asmprogger schrieb: > noch überhaupt wissen, > was er tun soll. Genau das ist das Hauptproblem, es gibt nämlich für nichts eine universelle Lösung. Je mehr man über den Kontext wüßte, umso besser könnte man helfen. In der Regel nützt ein kurzer Interrupt nämlich garnichts, sondern der Worst-Case ist entscheidend. Z.B. die CPU ist gerade in einem UART-, I2C- oder sonstigem Interrupt beschäftigt, dann muß der kurze Interrupt erstmal schön lange warten, bis er ran darf. Daher wäre schonmal wichtig, ob es keine weiteren Interrupts zu berücksichtigen gibt. Denn wenn es eh keine weiteren Interrupts gibt, dann könnte auch ein Polling in der Mainloop die Lösung sein. Damit spart man schonmal 10 Zyklen für das Anspringen und Verlassen des Interrupts.
Oliver schrieb: > asmprogger schrieb: >> Da scheints wohl keinen sehr schönen/angenehmen Weg dafür zu >> geben. Ich werde dann die Register "per Hand" sichern. > > [...] ein paar Zeilen Assembler, die die caller-saved Register > pushen und poppen, das wars. Nein, su sichern sind die call-clobbered Register. Um die anderen, nämlich die callee-saved Register, kümmert sich bereits die gerufene Funktion. Zusätzlich müssen R0, R1 und SREG verarztet werden. Axel Schwenke schrieb: > Johann L. schrieb: >> Im Gegenteil, das ist ganz und garnicht einfach zu optimieren. >> Daß klar ist, was das Resultat der Optimierung sein soll, heißt >> noch lange nicht, daß es einfach zu implementieren ist. >> >> I.W. bedeutet es, fast das ganze avr-Backend des GCC neu zu schreiben. > > OK, dann war meine Opinion wohl doch zu Humble ;) > > In der Tat spreche ich von winz-ISR (nur dort fällt die "überflüssige" > Sicherung von R1 ins Gewicht). So etwas wie >
1 | volatile uint8_t t0u; |
2 | |
3 | ISR (TIMER0_OVF_vect) |
4 | {
|
5 | ++t0u; |
6 | }
|
Der Aufwand der Implementierung ist aber unabhängig von der Größe des übersetzten Codes. Im GCC müssten alle pseudo-Instruktionen, aka. insns, erweitert werden. Das Backend kennt rund 1400 insns. > das ist ein sehr typisches Konstrukt in meinen Programmen. Noch öfter > als Zähler, der bis 0 runterzählt und dann da bleibt: >
1 | volatile uint8_t countdown; |
2 | ...
|
3 | if ((uint8_t r=countdown)) |
4 | countdown= r-1; |
> > Dann bleibt wohl nur die Variante, das in (Inline) ASM zu schreiben. > Nicht portabel, aber irgendwas ist ja immer. Solche Countdown-Zähler benutze ich auch, und die tun prima in C; selbst bei zeitkritischen Anwendungen musste ich da nicht auf Assembler umstellen.
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.