Forum: Compiler & IDEs avr-gcc: Optimierung von indirekten Funktionsaufrufen


von N. G. (newgeneration) Benutzerseite


Lesenswert?

Hallo Forum,

Mir ist beim schreiben einer Library ein mögliches Optimierungsproblem 
aufgefallen:

Testcode:
1
typedef unsigned char uint8_t;
2
3
extern uint8_t (*fp)(uint8_t);
4
5
void foo(uint8_t (*param1)(uint8_t)) {
6
    fp = param1;
7
}
8
void bar(void) {
9
    fp(0xFF);
10
    fp(0xFF);
11
}
kompiliert mit avr-gcc -S -Os -Wall -mmcu=avr6 test_fp.c zu
1
foo:
2
    sts fp+1,r25
3
    sts fp,r24
4
    ret
5
6
bar:
7
    lds r30,fp
8
    lds r31,fp+1
9
    ldi r24,lo8(-1)
10
    eicall
11
    lds r30,fp
12
    lds r31,fp+1
13
    ldi r24,lo8(-1)
14
    eijmp
15
16
    .ident  "GCC: (GNU) 5.3.1 20160106"
Man sieht: die Adresse der Funktion wird 2 mal, bzw. vor jedem 
Funktionsaufruf neu geladen. Wenn man jetzt (so wie ich) häufig diese 
indirekten Funktionsaufrufe benutzt, geht das sowohl zulasten der 
Laufzeit als auch zulasten des Speichers.

Jetzt meine Frage:
Ist das eine Optimierungsproblem, oder kann/muss ich den Compiler 
irgendwie dazu überreden, die Adresse in den Registern zu halten?

Wie man im erzeugten asm-Code sehen kann, ist mein avr-gcc die Version 
5.3.1 aus dem trunk vom 06.01.2016, also nicht der neuste.
Ich habe leider im Moment keinen anderen Compiler auf der Festplatte, 
deshalb kann ich da nichts vergleichen.

Kann jemand, der eine qualifiziertere Meinung hat als ich mal etwas dazu 
sagen?

Mit freundlichen Grüßen und frohe Weihnachten,
N.G.

von N. G. (newgeneration) Benutzerseite


Lesenswert?

Was mir noch einfällt:

könnte das Z-Register (also r31:r30) geclobbert werden?
Schließlich wird zwischen den beiden Aufrufen eine dem Compiler 
unbekannte Funktion ausgeführt, die streng genommen tun kann, was sie 
will.

r24 wird ja für die Parameterübergabe verwendet und auch als Register 
für den Rückgabewert, deswegen wird r24 nochmal neu geladen, da der 
Compiler davon ausgehen muss, dass r24 sich geändert hat.

Ist das beim Z-Register ähnlich?

Dann wäre das Verhalten verständlich.

von Stefan E. (sternst)


Lesenswert?

N. G. schrieb:
> könnte das Z-Register (also r31:r30) geclobbert werden?

Ja. R30/31 gehört zu den "call-used" Registern.

http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_reg_usage

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Man könnte es einmal laden und jedesmal ins Z-Register transportieren. 
Allerdings müsste man dann dieses Registerpaar sichern (wenn nicht 
ausgerechnet in main) und der Gesamtaufwand wäre beträchtlich grösser.

von Markus F. (mfro)


Lesenswert?

Wenn's denn unbedingt sein muß, kann man mit gcc Register mit globalen 
Variablen vorbelegen:
1
     register int *foo __asm__("a4");

Natürlich muß jede Übersetzungseinheit das wissen, sonst überschreibt 
sie deine Vorbelegung. Dazu muß das Statement entweder überall 
auftauchen oder dem Compiler über die Kommandozeile verständlich gemacht 
werden:
1
-ffixed-a4

Außerdem mußt Du natürlich alle Libraries, deren Funktionen irgendwann 
mal gerufen werden könnten, mit dieser Option übersetzen.

Ob's das wert ist?

: Bearbeitet durch User
von N. G. (newgeneration) Benutzerseite


Lesenswert?

Hallo zusammen,

naja, da hatte ich ja dann leider doch Recht, schade.

Dafür inline-asm zu benutzen oder gar das ABI zu ändern, muss meiner 
Meinung nach nicht sein (vor allem, weil ich ja eine Library baue, und 
der User dann auch diese ABI-Änderung benutzen müsste).


Vielen Dank,
N.G.

von Bernd K. (prof7bit)


Lesenswert?

Was ist denn die konkrete Anwendung?

Evtl könnte vielleicht Deine "Library" an der Stelle auch eine in ihrem 
Header als Prototyp deklarierte hook-Funktion aufrufen die später von 
der Anwendung irgendwo anders implementiert werden muss und statisch 
gelinkt wird. Das setzt dann halt voraus dass spätestens zur Linkzeit 
bekannt wird welches Modul den Hook letztendlich implementiert.

Wenn so ein Konstrukt mitsamt mit der "Library" und der Anwendung mit 
-flto optimiert und gelinkt wird dann ergibt das sehr effizienten Code 
und die meisten Indirektionen über Modulgrenzen hinweg lösen sich 
komplett in Luft auf. Ich hab schon Fälle gesehen bei denen 2 ineinander 
verschachtelte Hook-Funktionen (über drei Module hinweg) die nur dazu 
dienten einen GPIO-Pin zu treiben am Ende nach der LTO zu insgesamt zwei 
Inline-Assemblerbefehlen an der aufrufenden Stelle zusammengeschrumpft 
sind (implodiert statt geschrumpft trifft es besser).

Das war allerdings mit dem arm-gcc, ob der avr-gcc das genauso gut kann 
vermag ich nicht zu sagen, aber die Chancen stehen gut.

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