PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Register für Variable reservieren



s.frings
18.04.2010, 17:33
Hallo,
ich würde gerne den folgenden Code auf Performance optimieren:

// Timer 0 Unterbrechung (100khz)
ISR(TIMER0_COMP_vect) {
static uint8_t prescaler;
if (++prescaler==100) {
prescaler=0;
system_timer++;
}
}
Ich glaube, dass es hier wegen der hohen Aufruf-Frequenz gut wäre, die Variable "prescaler" in ein Register zu legen. Ich habe die Variable außerhalb der Funktion (also global) verschoben und so deklariert:

register uint8_t prescaler asm("r2");
Das führt leider zu folgender Fehlermeldung:

Fehler: expected »=«, »,«, »;«, »asm« or »__attribute__« before »asm«
Wie macht man es richtig?

XBert
18.04.2010, 17:49
Ich machs immer so:

uint16_t var = 6518;
asm ("" : "+r" (var));

Wobei ich allerdings nicht denke, dass eine 8Bit Variable auf 2 Register verteilt wird. Das würde mMn keinen Sinn machen. (Bei Computer-Prozessoren kann eher das umgekehrte auftreten, also das z.b 4 8Bit Variablen auf 4 32Bit Register aufgeteilt werden, da es schneller ist, als ein 32Bit Register mit 4 8Bit Variablen zu verwalten)

Das einzige was an deinem Code "optimiert" werden könnte ist statt system_timer++; ein Pre-Increment zu machen, also: ++system_timer;
Ich nehme aber mal an das der Compiler das ohnehin tut (ohne es getestet zu haben). Dazu müsstest du dir den Assemblecode ansehen

LG

sternst
18.04.2010, 21:15
Ich machs immer so:Was genau soll das bewirken?
Das was s.frings gerne hätte ist es jedenfalls nicht.


Wie macht man es richtig?
Kurze Antwort:
"asm" in "__asm" ändern, oder gnu99 als Standard verwenden.

Lange Antwort:
Richtig macht man es gar nicht. Die potentiellen Probleme lohnen den Gewinn (läppische 4 Clocks in diesem Fall) fast nie. Wenn du diese Mini-ISR optimieren willst, dann implementiere sie direkt als Assembler-Funktion (mit der Variable im Speicher, nicht ausschließlich im Register). Der mögliche Gewinn ist höher, und das ohne die möglichen Nebenwirkungen.

Besserwessi
18.04.2010, 22:02
Das sehe ich auch so, wenn schon ASM dann die ganze ISR. Einen Pumkt den man da verbessern kann ist schon mal das Sichern der Register - GCC legt da immer noch ein Zero Register an, auch wenn man es oft gar nicht nutzt.
Oft kann man den Prescaler auch abwärts Zählen lassen von 100 nach 0, das geht auch etwas schneller.


Vermutlich könnte man noch mehr Gewinnen, wenn man die Zeit gar nicht von Hand hochzählt, sondern direkt den Timer mit einem Passenden Vorteiler (z.B. 64) laufen läßt und dann ggf. zur Ausgabe umrechnet von den krummen Timer Ticks in µs.

s.frings
19.04.2010, 08:34
Einen Krummen Timer möchte ich nicht haben, denn dann jittert mein Mikroskunden Zähler, was wiederum verheerende Auswirkungen auf andere Programmteile hätte (z.B. die Berechnung der Aktuellen Geschwindigkeit).

Mir ist schon klar, daß meine ISR recht teuer ist, daber nach den obigen Kommentaren werde ich sie wohl unverändert lassen.

Wenn ich könnte, würde ich den Quartz austauschen, so daß ich den Vorteiler nutzen kann, aber dann funktioniert die USB Schnittstelle nicht mehr.

askazo
19.04.2010, 12:27
Wenn ich könnte, würde ich den Quartz austauschen, so daß ich den Vorteiler nutzen kann, aber dann funktioniert die USB Schnittstelle nicht mehr.
Du könntest ja auch für den Timer einen externen Takt (z.B. einen Oszillator) nutzen. Dann läuft Deinen USB-Schnittstelle und Du kannst trotzdem Deinen Code optimieren.

Gruß,
askazo

Besserwessi
19.04.2010, 18:50
Die ISR ganz in ASM könnte schon etwas bringen, und ist auch nicht so komliziert.

SprinterSB
23.04.2010, 19:16
Die ISR als


#include <avr/io.h>
#include <avr/interrupt.h>

uint32_t volatile system_timer;

ISR(...,ISR_NAKED)
{
static uint8_t prescaler;
asm volatile (" ... " :: "s" (&prescaler), "s" (&system_timer));
}


und als asm ... ohne Gewähr (den quoten darfst selber ;-))


push r24
in r24,__SREG__
push r24

lds r24, %0
subi r24,-1
cpi r24,100
brne 0f

lds r24, %1
subi r24, -1
sts %1, r24
lds r24, %1+1
sbci r24, -1
sts %1+1, r24
lds r24, %1+2
sbci r24, -1
sts %1+2, r24
lds r24, %1+3
sbci r24, -1
sts %1+3, r24
ldi r24,0
0:
sts %0,r24

pop r24
out __SREG__,24
pop r24
reti

s.frings
23.04.2010, 20:36
Danke SprinterSB, das war genau der Tipp, den ich gesucht hatte. Leider habe ich Deinen Beitrag zu spät gelesen - und daher das ganze anhand diverser Tutorials erarbeitet. Mein Forschungsergebnis entspricht fast exakt Deinem Vorschlag (was für ein Zufall :-))

Guckst Du: https://www.roboternetz.de/phpBB2/viewtopic.php?p=498523#498523

s.frings
08.06.2010, 18:37
Es hat sich inzwischen herausgestellt, dass die Verwendung von Register Variablen bei Verwendung der avr libc unerwartete Seiteneffekte bewirkt hat.

Das Problem ist, dass ich in "meinen" Sourcen zwar ein Register reserviert habe, aber die avr libc weiss davon nichts. Spätestens wenn man nun z.B. mit printf etwas ausgibt, passieren seltsame Dinge.

In meinem Fall kam erschwerend dazu, dass ich das Register in einer Interrupt-Routine beschrieben habe. Sobald irgendeine Library das Register anderweitig verwendet (was sehr wahrscheinlich ist), kommt es zu Fehlfunktionen.

SprinterSB
10.06.2010, 09:13
Es hat sich inzwischen herausgestellt, dass die Verwendung von Register Variablen bei Verwendung der avr libc unerwartete Seiteneffekte bewirkt hat.

Nö, unerwartet ist das überhaupt nicht.


Das Problem ist, dass ich in "meinen" Sourcen zwar ein Register reserviert habe, aber die avr libc weiss davon nichts. Spätestens wenn man nun z.B. mit printf etwas ausgibt, passieren seltsame Dinge.

In meinem Fall kam erschwerend dazu, dass ich das Register in einer Interrupt-Routine beschrieben habe. Sobald irgendeine Library das Register anderweitig verwendet (was sehr wahrscheinlich ist), kommt es zu Fehlfunktionen.

Es ist ohne weiteres möglich globale Registervariablen zu verwenden.

Das Problem in deinem Code ist, daß das von dir definierte Register nicht global ist. Damit ein Register global ist, müssen

in allen Module, die die Registervariable verwenden, diese bekannt gemacht werden. Dazu schreibt man die Register-Definition zweckmässigerweise in einen Header
alle Module, auch die, welche die Variable nicht verwenden, werden mit -ffixed-* übersetzt (gcc). Für eine 16-Bit Registervariable in R2/R3 also -ffixed-2 -ffixed-3. Beachte, daß Bibliotheken nicht mit diesen Schaltern übersetzt wurden. gcc kann alle Register > 2 explizit verwenden, und die Register 0 und 1 verwendet er implizit. Daher kann innerhalb einer Lib-Routine diese Register verwenden.


Um globle Register, die in ISRs verwendet werden, sicher zu verwenden, gibt es also mehrere möglichkeiten:

Keine Lib-Funktionen verwenden, d.h. keine explizit in die Quelle schreiben.
Alle explizit aufgerufenen Lib-Funktionen mit SEI/CLI atomar machen
Eigene Variante der Libs übersetzen mit -ffixed-* wie benötigt
Verwendung von prologue-saves, epilogue-restores anpassen (libgcc.S anpassen)
Libs verwenden und sich mit einem grep o.ö. versichern, daß keine der verwendeten Lib-Funktionen auf die Register zugreift.
Implizit verwendete Lib-Funktionen sollten unkritisch sein. Ausnahme: prologue_saves, epilogue_restores und evtl das setjmp/longjmp-Zeug


Immerhin hast du durch den "Fehler" ein bisschen mehr über die Arbeitsweise eines Compilers gelernt :-)

Übrigens ist es garnicht sooo viel Arbeit, ein eigenes printf zu schreiben.

s.frings
10.06.2010, 12:57
Ich breche jedoch auch keinen Zacken aus der Krone, wenn ich anstatt eines Register eine Variable im SRAM verwende. Ganz so Performance-Kritisch ist die Sache nämlich nicht.