Forum: Compiler & IDEs avr-gcc: den Zugriff auf register variablen optimieren?


von Chris (Gast)


Lesenswert?

Hallo,

ich versuche mit dem avr-gcc effizient auf register variablen 
zuzugreifen.
Leider macht er aus
1
register i asm("r17")
2
...
3
--i

folgendes
1
    mov r24,r17
2
    subi r24,lo8(-(-1))
3
    mov r17,r24

Der Compiler scheint register variablen wie Ram zu behandeln:
laden, dekrementieren, speichern. Es ist egal welches Register ich 
benutze (high oder low).
Wie kann ich den Compiler dazu bringen ein 'subi r17' oder 'dec' zu 
generieren?
(avr-gcc version ist 4.3.4)

Vielen Dank im Voraus,

Chris

von Tom M. (tomm) Benutzerseite


Lesenswert?

Chris schrieb:
> Wie kann ich den Compiler dazu bringen ein 'subi r17' oder 'dec' zu
> generieren?

Gar nicht. Wenn du wirklich was optimieren möchtest, schreib Funktionen 
direkt in ASM und linke sie mit dem übrigen Programm zusammen. Dazu 
gibt's ein Kapitel in der avr-libc Doku inkl. Beispiel.

Welchen Code generiert der Compiler für i-- ?
Wie wirken sich die Optimierungen aus (-O2 oder -Os)?

von Yalu X. (yalu) (Moderator)


Lesenswert?

Chris schrieb:
> Der Compiler scheint register variablen wie Ram zu behandeln:
> laden, dekrementieren, speichern.

In welchem Kontext passiert das? Hast du ein kleines, kompilierbares
Testprogramm, wo man das Problem sehen kann?

Unabhängig davon ist es aber immer etwas gefährlich, C-Variablen feste
Register zuzuweisen und bringt oft mehr Nachteile als Vorteile.

von Chris (Gast)


Lesenswert?

@tom:
Also der GCC kann mir doch ganz toll ganze Codeblöcke wegoptimieren
(wenn man mal das volentile vergisst ;-) und dann kann er diese
einfache Registeroptimierung nicht ???

@Yalu
Ich probiere mal das runterzubrechen in ein kleines Programm. Mir ist es 
bei der Statevariabe aufgefallen, die in der Interruptrutine verändert 
wird.

Eine Registervariable bringt mir  schon ca 100 Bytes weniger, weil state 
naürlich als volatile deklariert ist und daher immer aus dem Ram neu 
geladen und gespeichert wird wird.
Und das Programm macht nicht so viel, auf ein Register kann ich (bzw. 
der Compiler) gut verzichten. Und die Avr-Lib wird auch nicht benutzt, 
also auch von hier keine Gefahr.

Ich versuche mal ein kleines Beispiel zu machen, vielleicht bring das 
neue Erkenntnisse.

Viele Grüsse,

Chris

von Oliver (Gast)


Lesenswert?

"register" ist ein Relikt aus dem Ur-C-PDP-11-Pleistozän. Aktuelle gcc's 
nehmen das höchstens noch als unverbindlichen Wunsch des Programmieres 
zur Kenntnis, und ignorieren es, wenn immer erforderlich. "volatile 
register" kann der gcc schon gar nicht, was u.a. daran liegt, daß das 
gar keinen Sinn ergibt.

Wenn du deine volatile state-variable am Anfang der ISR in eine lokale 
Variable umkopierst, und am Ende wieder zurück, dann hat der Compiler 
dazwischen alle Optimierungsmöglichkeiten. Den würde ich da einfach 
machen lassen, und ihm nicht ins Handwerk pfuschen.

Wenn es tatsächlich auf jeden einzelnen Zyklus und jedes einzelne Byte 
ankommt, nimm, wie schon gesagt wurde, Assembler.

Oliver

von Peter D. (peda)


Lesenswert?

Nicht ganz alte AVRs haben ein GPIOR0 (ATtiny auch GPIOR1,2), welches im 
bitadressierbaren IO-Bereich liegt.


Peter

von Chris (Gast)


Lesenswert?

Ja dass hört sich gut an, das den Compiler einfach machen zu lassen. 
Leider ist er doch nicht so schlau. Wie gesagt, mit dem Holzhammer an 
eine Registervariable gebunden spart mir schon 100 Byte und ich bin halt 
knapp über den 2K vom ATtiny2313. und wenn er den Zugriff auch noch 
besser machen würde hätte ich noch mehr davon. Im Hauptprogramm wird die 
Statevariable auch sehr häufig abgefragt.
Vielleicht wenn man das Programm mit --complete (oder wie heisst die 
option?) compiliert kann der Compiler das besser optimieren. (hat da 
jemand Erfahrung?)

Zu Assembler kann ich mich noch nicht durchringen, solange ich es in C 
hinbekomme.

Gruss

Chris

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wenn du r17 als Registervariable verwendest fliegt dir der Code um die 
Ohren, wenn das Register zur Parametrübergabe verwendet wird. Du kannst 
nicht einfach eine Registervariable verwenden und erwarten daß ein 
Compiler eine dynamische ABI-Anpassung vornimmt.

Und überhaupt: Ist die Registervariable lokal oder global?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wenn du Code sparen willst, dann:

Fass Variablen, die zueinander gehören, zu Strukturen zusammen und 
greife indirekt darauf zu. Das spart dann pro Zugriff 2 Bytes. Um das 
sinnvoll nutzen zu können, braucht das Programm aber eine gewisse 
Grundstruktur, weil der Code ansonsten nur Pointervariablen am 
Rumschaufeln ist (wegen der mageren Ausstattung der AVRs mit 
Adressregistern).

von Chris (Gast)


Lesenswert?

Hallo,

hier habe ich ein kleines Testprogramm geschrieben:
1
#include <stdint.h>
2
3
volatile register uint8_t state __asm__("r24");
4
5
// damit die Schleife nicht komplett wegoptimiert wird:
6
static volatile uint8_t tmp; 
7
8
void main() {
9
//register uint8_t state ;
10
11
   state=100;
12
   tmp=state;
13
14
   do {
15
        tmp=state;      
16
   }while (--state);   
17
}

Mit -O0 sieht man schön dass r24 als 'Speicherzelle' betrachtet wird die 
kein register ist und nicht rechnen kann:
1
        ldi r24,lo8(100)         ;  state,
2
.LM2:
3
        mov r25,r24      ;  state.0, state
4
        sts tmp,r25      ;  tmp, state.0
5
.L2:
6
.LM3:
7
        mov r25,r24      ;  state.1, state
8
        sts tmp,r25      ;  tmp, state.1
9
.LM4:
10
        mov r25,r24      ;  state.2, state
11
        subi r25,lo8(-(-1))      ;  state.3,
12
        mov r24,r25      ;  state, state.3
13
        mov r25,r24      ;  state.4, state
14
        tst r25          ;  state.4
15
        brne .L2         ; ,

Leider kann der Optimierer -O3 das nicht mehr richten :-((
1
        ldi r24,lo8(100)         ;  state,
2
.LM2:
3
        ldi r25,lo8(100)         ;  state.0,
4
        sts tmp,r25      ;  tmp, state.0
5
.L2:
6
.LM3:
7
        mov r18,r24      ;  state.1, state
8
        sts tmp,r24      ;  tmp, state.1
9
.LM4:
10
        subi r18,lo8(-(-1))      ;  state.3,
11
        mov r24,r18      ;  state, state.3
12
        brne .L2         ; ,

So, hat jemand eine Idee wie man dem Compiler helfen kann ?

Chris

von Oliver (Gast)


Lesenswert?

Oliver schrieb:
> "volatile register" kann der gcc schon gar nicht,

Je nun, was wollte ich dir mit meinen Worten wohl sagen?

Oliver

von Oliver (Gast)


Lesenswert?


von Stefan E. (sternst)


Lesenswert?

> So, hat jemand eine Idee wie man dem Compiler helfen kann ?

Indem man ihm nicht sein Haupt-Arbeits-Register klaut. R24 ist eine noch 
viel beschissenere Wahl als r17. Beschränke dich auf die Register 2 bis 
7, oder lass es ganz bleiben.

von Stefan E. (sternst)


Lesenswert?

Chris schrieb:
> Eine Registervariable bringt mir  schon ca 100 Bytes weniger,

Chris schrieb:
> Leider kann der Optimierer -O3 das nicht mehr richten

Wenn es dir um die Codegröße geht, dann benutze -Os, nicht -O3.

von Stefan E. (sternst)


Lesenswert?

BTW:

Oliver schrieb:
> "volatile
> register" kann der gcc schon gar nicht, was u.a. daran liegt, daß das
> gar keinen Sinn ergibt.

Was ergibt daran keinen Sinn?
In Bezug auf Sinn und Zweck von volatile sehe ich da keinen Unterschied 
zur "normalen" Variable.

von Chris (Gast)


Lesenswert?

@Oliver:
>> "volatile register" kann der gcc schon gar nicht,
>Je nun, was wollte ich dir mit meinen Worten wohl sagen?

Das weiss ich leider nicht was Du mir sagen willst.
Also der Compiler beschwert sich nicht.
Aber lassen wir doch das volatile vorm Register mal weg dann macht der 
Compiler dieses hier:
1
.LM1:
2
        ldi r25,lo8(100)         ;  tmp43,
3
        sts tmp,r25      ;  tmp, tmp43
4
.L2:
5
        .stabn  68,0,14,.LM2-.LFBB1
6
.LM2:
7
        sts tmp,r25      ;  tmp, ivtmp.16
8
        subi r25,lo8(-(-1))      ;  ivtmp.16,
9
        .stabn  68,0,15,.LM3-.LFBB1
10
.LM3:
11
        brne .L2         ; ,
12
        ldi r24,lo8(0)   ;  state,

Das Register r24 wird ganz zum Schluss mit 0 geladen. Ist ja auch 
korrekt so. Aber das will ich nun schon gar nicht.

Der GCC macht auf jeden Fall was ziemlich anderes. Vielleicht ja auch 
was anderes als im Manual steht. Macht aber Sinn wenn GCC das Register 
als Speicherstelle ansieht und halt jedes mal vor der Verwendung sich 
den Wert daraus holt. Und nicht nur einmal zum Schluss.

Gruss

chris

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Chris schrieb:
> @Oliver:
>>> "volatile register" kann der gcc schon gar nicht,
>>Je nun, was wollte ich dir mit meinen Worten wohl sagen?
> Das weiss ich leider nicht was Du mir sagen willst.

volatile bezieht sich auf Objekte in der Speicherklasse static storage, 
und da liegt eine explizite Registervariable nun mal nicht. Die liegt 
eben in der Speicherklasse register, wie angeordnet.

Von daher dient ein "volatile register ...asm" bestenfalls der eigenen 
Verwirrung; stätestens wenn das Register in einer ISR und im 
Normalo-Code verwendet wird, ist eine Bauchlandung vorprogrammiert (etwa 
Poll-Schleife auf so eine Variable).

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

avr-gcc-3.4.x:
1
warning: volatile register variables don't work as you might wish

avr-gcc-4.5.x:
1
warning: optimization may eliminate reads and/or writes to register variables
2
warning: call-clobbered register used for global register variable

Aber bekanntlich ignoriert man Warnungen am besten... sind ja keine 
Fehler ;-)

von Peter D. (peda)


Lesenswert?

Chris schrieb:
> Vielleicht wenn man das Programm mit --complete (oder wie heisst die
> option?) compiliert kann der Compiler das besser optimieren. (hat da
> jemand Erfahrung?)

--combine

Ja, ich benutze es gerne.
Es macht eine sehr effiziente Optimierung und findet fast jedes 
vergessene "volatile".
D.h. wenns danach nicht mehr geht, hat er Dir was wegoptimiert, was 
volatile sein muß.

Es müssen alle C-Files in einem Rutsch compiliert werden. Das geht am 
besten auf der Kommandozeile, *.c expandiert zu allen C-Files im 
aktuellen Verzeichnis.

Hier ein Beispiel (a.bat):

Beitrag "mehrere MC seriell über Datenbus verbinden (1Draht)"


Peter

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Hier mal ein kleines Beispiel:
1
register char state asm ("r2");
2
3
void bar (void);
4
5
void foo (void) 
6
{
7
   state = 100;
8
9
   while (1)
10
   {
11
        do 
12
        {
13
            bar();
14
        } while (state);
15
   }
16
}

avr-gcc 4.5 (ditto avr-gcc 3.4) macht daraus:
1
foo:
2
  ldi r24,lo8(100)   ;  20  *reload_inqi  [length = 2]
3
  mov r2,r24
4
.L2:
5
  call bar   ;  8  call_insn/3  [length = 2]
6
  rjmp .L2   ;  21  jump  [length = 1]

Und der Code ist so auch ok.

von Chris (Gast)


Lesenswert?

Johann L. schrieb:

> Aber bekanntlich ignoriert man Warnungen am besten... sind ja keine
> Fehler ;-)

Welche Warnungen ??

Compiliere bitte mit  GCC 4.3.4 und natürlich mit -ffixed-r24

Zu den Warnungen bei volatile register gibts einen Thread auf der 
AVR-GCC-Bug-Liste, das wurde anscheinend oft in den verschiedenen 
Versionen geändert.

Hilft mir aber nicht viel. Hast Du einen konstruktiven Vorschlag wie man 
effektiv eine Registervariable in C verwenden kann die in einer ISR 
verändert wird?

von Chris (Gast)


Lesenswert?

Seit Ihr schnell ....

Johann L. schrieb:
>Und der Code ist so auch ok.

Formaljuristisch ist er ok. Aber es soll natürlich die Abfrage auf 
state==0 nicht wegoptimiert werden. Bei einer statischen Variable macht 
man das mit volatile. Und was macht man bei einer Registervariablen ???

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Chris schrieb:

> Formaljuristisch ist er ok. Aber es soll natürlich die Abfrage auf
> state==0 nicht wegoptimiert werden. Bei einer statischen Variable macht
> man das mit volatile. Und was macht man bei einer Registervariablen ???

Selbst der Klassiker
1
asm volatile ("":"+r"(state));

bleibt wirkungslos... (avr-gcc 3.4, 4.3, 4.5), ebenso ein Verändern von 
state in der Schleife. Was willst da machen?

Verwende andere, robuste Techniken, um die Codegröße zu optimieren. Da 
ist zudem mehr zu holen. Auf den L-Regs sind eh kaum Operationen 
möglich. Wahrscheinlich wird dein Code mit der Registervariablen nur 
deshalb so klein, weil die hälfte in der Tonne landet...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Chris schrieb:

> Compiliere bitte mit  GCC 4.3.4 und natürlich mit -ffixed-r24

Ich hab hier net jede jemals veröffentliche avr-gcc Version ;-)

-ffixed-n änder nix am ABI. Es nimmt die Variable dem Registerallokator, 
wenn er Pseudoregister zuordnen will. Aber -ffixed verhindert nicht das 
Erzeugen von r24 per se, zB durch Funktionsaufrufe wie a = b / c zur 
libgcc.

Bestenfalls Register, die nicht vom ABI belegt werden (wie oben schon 
geschrieben r2-r7) sind für sowas zu gebrauchen.

In einer Anwendung mit avr-gcc 3.4 verwende ich globale 
Registervariablen, weil das 200000 Speicherzugriffe pro Sekunde spart. 
Im Endeffekt bringt's nicht sooo viel, auch nicht für die Codegröße (die 
übrigens kleiner ist als mit jeder 4-er Version, die ich je angetestet 
habe). Aber nun ist's in Code eben mal drinne... An Codegröße macht das 
einen Unterschied von -0.5%, also lächerlich. Aber da ich in der ISR 
jeden Tick brauche, bleibt's eben drin.

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.