Forum: Compiler & IDEs AVR-GCC: Funktionspointer in ISR


von Walter T. (nicolas)


Lesenswert?

Guten Morgen,
diesmal ist die Effizienz die Frage, die mich umtreibt. Ich habe eine 
ISR, die immer mit einer Millisekunde Takt ausgeführt wird, wobei 
unterschiedliche, kurze Funktionen ausgeführt werden müssen.

Im Moment sieht das ungefähr so aus:
1
// Liste aller momentan in ISR ausfuehrbarer Funktionen
2
typedef void (*voidFnct_t)(void);
3
voidFnct_t gl_isrfcns[8];
4
5
6
ISR (TIMER0_OVF_vect) // Takt 1kHz
7
{
8
9
  while(*gl_isrfcns) {
10
    voidFnct_t fkt = *gl_isrfcns;
11
    gl_isrfcns++;
12
    fkt();
13
  }
14
}
Das ist sehr komfortabel, da ich im Array "gl_isrfcns" einfach nur die 
benoetigten Funktionspointer anhängen oder wieder entfernen muß und die 
eigentliche Auswahl dessen, was in der ISR ausgeführt werden muß in den 
entsprechenden Modulen stattfindet. Nur habe ich den Overhead durch die 
Funktionszeiger.

Oder ist es sinnvoller auf ein anderes Konstrukt zu schwenken:
1
ISR (TIMER0_OVF_vect) // Takt 1kHz
2
{
3
  if (gl_isfkt0inisr) fkt0();
4
  if (gl_isfkt1inisr) fkt1();
5
  if (gl_isfkt2inisr) fkt2();
6
  if (gl_isfkt3inisr) fkt3();
7
  if (gl_isfkt4inisr) fkt4();
8
  if (gl_isfkt5inisr) fkt5();
9
  if (gl_isfkt6inisr) fkt6();
10
}

was weniger modular aber vermutlich schneller wäre?

Viele Grüße
Nicolas

von Bananen Joe (Gast)


Lesenswert?

Und woher weist du, dass deine von der ISR aufgerufenen Funktionen in 
der Zeit fertig werden und dass du nicht die nächste Funktion aufrufst 
während die vorherige noch läuft?
Deshalb ruft man aus Interruptroutinen keine Funktionen auf!
Du kannst aber in der ISR ein Flag setzen, das in der Hauptschleife 
kontrolliert und abgearbeitet wird, wenn bestimmte Bedingungen erfüllt 
sind.

von Uwe (de0508)


Lesenswert?

Guten Morgen,

ich schließe mich dem vorherigen Eintrag an, ereignisgesteuertes 
Programmieren in der Hauptprogrammschleife ist i.A. der zu wählende Weg.

Aber dieser Code enthält zwei entscheidende Fehler,
einmal logisch und zum anderen syntaktisch.
So würde das auch nicht funktionieren!
1
 while(*gl_isrfcns) {
2
    voidFnct_t fkt = *gl_isrfcns;
3
    gl_isrfcns++;
4
    fkt();
5
  }

von Rolf Magnus (Gast)


Lesenswert?

Nicolas S. schrieb:
> Nur habe ich den Overhead durch die Funktionszeiger.

Der ist vernachlässigbar gegenüber der Sicherung und Wiederherstellung 
aller Register, die bei jedem Funktionsaufruf aus der ISR heraus gemacht 
wird.

von Peter II (Gast)


Lesenswert?

Bananen Joe schrieb:
> Und woher weist du, dass deine von der ISR aufgerufenen Funktionen in
> der Zeit fertig werden und dass du nicht die nächste Funktion aufrufst
> während die vorherige noch läuft?
das weiss man ziemlich genau, weil nur eine ISR gleichzeitig laufen 
kann. (bei einem Atmel ohne selber an dem flags rumzuschrauben)

> Deshalb ruft man aus Interruptroutinen keine Funktionen auf!
nein, das ist nicht der Grund. Der Grund ist das dabei der Overhead sehr 
gross wird weil alle Register gesichert werden.

von Oliver (Gast)


Lesenswert?

Nicolas S. schrieb:
> Nur habe ich den Overhead durch die
> Funktionszeiger.

Das ist natürlich absolut inakzeptabel ;)

Ernsthaft, über wieviel Takte "overhead" redest du da? Und lohnt es 
sich, sich darüber überhaupt Gedanken zu machen?

Oliver

von Syliosha (Gast)


Lesenswert?

Nebenbei ist der Overhead beim nutzen von Funktionspointern gegenüber 
Funktionsaufrufen. Beide sind identisch.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Lass dir doch einfach den generierten Assemblercode für beide Varianten 
ausgeben und zähle darin die Taktzyklen und die Programmbytes.

von Walter T. (nicolas)


Lesenswert?

Uwe S. schrieb:
> So würde das auch nicht funktionieren!

Stimmt, das Beispiel ist etwas übervereinfacht. Es funktioniert so:
1
typedef void (*voidFnct_t)(void);
2
voidFnct_t gl_isrfcns[6];
3
4
ISR (TIMER0_OVF_vect) // Takt 1kHz
5
{
6
  voidFnct_t *ptr = gl_isrfcns;
7
  while( *ptr != NULL ) {
8
    voidFnct_t fkt = *ptr;
9
    ptr++;
10
    fkt();
11
  }
12
}
13
14
[...]
15
16
int main(void) {
17
  gl_isrfcns[0] = encoder_poll;
18
  gl_isrfcns[1] = glcd_timer;
19
  gl_isrfcns[2] = NULL;
20
 
21
  [...]
22
}


Syliosha schrieb:
> Nebenbei ist der Overhead beim nutzen von Funktionspointern gegenüber
> Funktionsaufrufen. Beide sind identisch.

Der Aufwand wird schon etwas größer sein, da er hier die Funktionen 
nicht mehr inlinen kann. Aber wie groß der Mehraufwand ist, kann ich 
momentan nicht abschätzen, weil gleichzeitig auch die *.lss-Datei 
deutlich unübersichtlicher wird.

Oliver schrieb:
> Ernsthaft, über wieviel Takte "overhead" redest du da? Und lohnt es
> sich, sich darüber überhaupt Gedanken zu machen?

Das ist die Frage, die ich klären will.

Viele Grüße
Nicolas

von Walter T. (nicolas)


Lesenswert?

Mit Funktionszeigerspielen: 12568 bytes (Prog mem) 1238 bytes (Data mem)
Mit Funktionsaufrufen:      12516 bytes (Prog mem) 1238 bytes (Data mem)

Kann ich da mit 26 Befehlen Unterschied je ISR-Aufruf rechnen?

von Falk B. (falk)


Lesenswert?

Schau dir im .lss File die ISR an.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Rolf Magnus schrieb:
> Der ist vernachlässigbar gegenüber der Sicherung und Wiederherstellung
> aller Register, die bei jedem Funktionsaufruf aus der ISR heraus gemacht
> wird.

Was verleitet Dich zur Annahme, daß ein Funktionsaufruf aus einer ISR 
heraus etwas anderes wäre als ein Funktionsaufruf außerhalb einer ISR?

Register etc. werden bei Aufruf der ISR gesichert, und beim Beenden der 
ISR wiederhergestellt.

Sonst aber nicht; wozu auch?

von Oliver (Gast)


Lesenswert?

Nicolas S. schrieb:
> Kann ich da mit 26 Befehlen Unterschied je ISR-Aufruf rechnen?

Nein.

Es gibt nur eine Möglichekeit, das rauszufinden: Assemblercode ansehen, 
und nachzählen.

Wenn du das nicht kannst, dann brauchst du das auch nicht. Vergiß die 
paar Zyklen, und kümemr dich um wichtigeres.

Oliver

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Gehen wir von einer F_CPU von 16 MHz aus, dann wird die ISR in Abständen 
von 16000 Ticks aufgerufen.

Bei einem ISR-Overhead von 100 Ticks durch indirekte Aufrufe wären das 
rund 0.6 % der Zeit, die der ISR verbleiben...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rufus Τ. Firefly schrieb:
> Was verleitet Dich zur Annahme, daß ein Funktionsaufruf aus einer ISR
> heraus etwas anderes wäre als ein Funktionsaufruf außerhalb einer ISR?

Darum geht's ja nicht.

Wenn man über einen Funktionszeiger aufruft, dann muss der Compiler
grundsätzlich alle Register sichern, die eine x-beliebige gerufene
Funktion gemäß ABI zerstören darf.

Wenn man die Funktion direkt aufruft, kann der Compiler ggf. direkt
feststellen, welche Register tatsächlich gesichert werden müssen.

Falls die gerufenen Funktionen hinreichend komplex sind, ist das aber
egal.  Falls sie aber nur an einem Pin wackeln oder sowas, kann der
Unterschied erheblich sein.

von Peter II (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Wenn man die Funktion direkt aufruft, kann der Compiler ggf. direkt
> feststellen, welche Register tatsächlich gesichert werden müssen.

kann er eigentlich nie. Nur wenn er sie inlined, dann wird sie aber auch 
nicht mehr als funktion aufrufen.

von Hans (Gast)


Lesenswert?

Funktionen innerhalb der Kompiliereinheit kann er doch auch aufrufen 
ohne alles wegsichern zu müssen, oder?

von Rolf Magnus (Gast)


Lesenswert?

Rufus Τ. Firefly schrieb:
> Rolf Magnus schrieb:
>> Der ist vernachlässigbar gegenüber der Sicherung und Wiederherstellung
>> aller Register, die bei jedem Funktionsaufruf aus der ISR heraus gemacht
>> wird.
>
> Was verleitet Dich zur Annahme, daß ein Funktionsaufruf aus einer ISR
> heraus etwas anderes wäre als ein Funktionsaufruf außerhalb einer ISR?

Vor allem die Tatsache, daß ich es im generierten Assembler-Code gesehen 
habe. Hier mal ein ganz einfaches Beispiel:
1
#include <avr/interrupt.h>
2
3
void foo()
4
{
5
}
6
7
void (*fun)() = foo;
8
9
int main()
10
{
11
    fun();
12
}
13
14
ISR(INT0_vect)
15
{
16
    fun();
17
}
18
19
ISR(INT1_vect)
20
{
21
}

übersetzt mit avr-gcc isr_test.c -mmcu=atmega8 -c -S -o- -O3
Aus main() wird dabei folgendes:
1
        lds r30,fun
2
        lds r31,fun+1
3
        icall
4
        ret

Aus INT1_vect (also ohne Aufruf der Funktion) folgendes:
1
        push r1
2
        push r0
3
        in r0,__SREG__
4
        push r0
5
        clr __zero_reg__
6
/* prologue: Signal */
7
/* frame size = 0 */
8
/* stack size = 3 */
9
.L__stack_usage = 3
10
/* epilogue start */
11
        pop r0
12
        out __SREG__,r0
13
        pop r0
14
        pop r1
15
        reti

und aus INT0_vect das hier:
1
        push r1
2
        push r0
3
        in r0,__SREG__
4
        push r0
5
        clr __zero_reg__
6
        push r18
7
        push r19
8
        push r20
9
        push r21
10
        push r22
11
        push r23
12
        push r24
13
        push r25
14
        push r26
15
        push r27
16
        push r30
17
        push r31
18
/* prologue: Signal */
19
/* frame size = 0 */
20
/* stack size = 15 */
21
.L__stack_usage = 15
22
        lds r30,fun
23
        lds r31,fun+1
24
        icall
25
/* epilogue start */
26
        pop r31
27
        pop r30
28
        pop r27
29
        pop r26
30
        pop r25
31
        pop r24
32
        pop r23
33
        pop r22
34
        pop r21
35
        pop r20
36
        pop r19
37
        pop r18
38
        pop r0
39
        out __SREG__,r0
40
        pop r0
41
        pop r1
42
        reti


> Register etc. werden bei Aufruf der ISR gesichert, und beim Beenden der
> ISR wiederhergestellt.

Ja, aber nur genau die, die die ISR für sich selbst braucht - es sei 
denn, eine Funktion wird aufgerufen. Dann ist unbekannt, welche Register 
von der Funktion geändert werden.

von Ralf G. (ralg)


Lesenswert?

Rolf Magnus schrieb:
> Ja, aber nur genau die, die die ISR für sich selbst braucht - es sei
> denn, eine Funktion wird aufgerufen. Dann ist unbekannt, welche Register
> von der Funktion geändert werden.

Mit meinen bescheidenen Programmierkenntnissen würde ich das sogar noch 
etwas einschränken [Hoffe, ich erinnere mich richtig]: Die 
'push-pop-Orgie' wird dann eingefügt, wenn sich die Funktion in einem 
anderen Modul befindet oder, wie oben, über einen Zeiger aufgerufen 
wird. Eine Funktion innerhalb der gleichen Datei wird vom GCC 
entsprechend durchleuchtet (evtl. auch ge-inlined) und in der ISR werden 
nur die wirklich benötigten Register gerettet.

von Peter II (Gast)


Lesenswert?

Ralf G. schrieb:
> Eine Funktion innerhalb der gleichen Datei wird vom GCC
> entsprechend durchleuchtet (evtl. auch ge-inlined) und in der ISR werden
> nur die wirklich benötigten Register gerettet.

wenn sie ge-inlined dann ja, sonst meines wissens nicht. Denn dafür 
müsste sich der compiler von jeder funtion merken welche Register sie 
verwendet.

von Oliver (Gast)


Lesenswert?

Peter II schrieb:
> Denn dafür
> müsste sich der compiler von jeder funtion merken welche Register sie
> verwendet.

Geh mal davon aus, daß der sich noch sehr viel mehr merkt...

Oliver

von Peter II (Gast)


Lesenswert?

Oliver schrieb:
> Geh mal davon aus, daß der sich noch sehr viel mehr merkt...

dann versteckt er das verhalten so gut, das ich es noch nicht beobachten 
konnte. Jeder Funktionsaufruf führe dazu das alle Register gesichert 
wurden.

von Falk B. (falk)


Lesenswert?

@ Peter II (Gast)

>dann versteckt er das verhalten so gut, das ich es noch nicht beobachten
>konnte. Jeder Funktionsaufruf führe dazu das alle Register gesichert
>wurden.

Na, es sind viele, aber nicht alle 32.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Oliver schrieb:
> Peter II schrieb:
>> Denn dafür müsste sich der compiler von jeder funtion
>> merken welche Register sie verwendet.
>
> Geh mal davon aus, daß der sich noch sehr viel mehr merkt...

Das stimmt zwar, aber die o.g. Optimierung wird für Funktionen nicht 
ausgeführt, d.h. auch wenn die Implementierung der Funktion bekannt ist, 
wird dieses Wissen im Caller nicht dazu verwendet, die Registerlast zu 
verkleinern.

Ausnahmen sind einige Funktionen in der libgcc, die in Assembler 
implementiert sind und deren Register-Fußabdruck dem Compiler explizit 
eingehämmert wurden.

Ansonsten gilt: Eine Funktion, die (im compilierten Code) aufgerufen 
wird, wird wie eine Black Box behandelt, d.h. alle call-clobbered 
Register sind als zerstört anzusehen.

Was der Compiler machen kann ist Inlining (kein Blach Box-Overhead) oder 
partielles Inlining oder Function Cloning (Prototyp wird an den Call 
angepasst).  In den beiden letzten Fällen ensteht der Black Box Overhead 
durch den verbleibenden Call.

Ob der Call nun direkt oder indirekt ausgeführt wird ist ziemlich 
Wurscht, nur ist bei einem direkten Aufruf die Wahrscheinlichkeit 
wesentlich größer, daß der Compiler erkennt, wie der Code des Callie 
denn genau lautet.

Warum die o.g. Optimierung von GCC nicht durchgeführt wird — da müsst 
ihr die GCC-Entwickler fragen.  Wahrscheinlich wie bei so vielen anderen 
Dingen auch:  Es ist nicht implementiert, weil es bislang niemandem 
wichtig genug war, es einzubauen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Das Sichern der Register scheint tatsächlich nur bei geinlineten
Funktionsaufrufen optimiert zu werden. Ok, Johann hat die Ausnahmen
dieser Regel genannt.

C:
1
#include <stdint.h>
2
#include <avr/interrupt.h>
3
4
volatile uint8_t dummy;
5
6
void foo()
7
{
8
  dummy = 1;
9
}
10
11
void foo_noinline() __attribute__((noinline));
12
void foo_noinline()
13
{
14
  dummy = 1;
15
}
16
17
ISR(INT0_vect)
18
{
19
  foo();
20
}
21
22
ISR(INT1_vect)
23
{
24
  foo_noinline();
25
}

Daraus von GCC 4.8.1 mit -Os generierter Assembler-Code:
1
foo:
2
  ldi r24,lo8(1)
3
  sts dummy,r24
4
  ret
5
6
foo_noinline:
7
  ldi r24,lo8(1)
8
  sts dummy,r24
9
  ret
10
11
__vector_1:
12
  push r1
13
  push r0
14
  in r0,__SREG__
15
  push r0
16
  clr __zero_reg__
17
  push r24
18
  ldi r24,lo8(1)
19
  sts dummy,r24
20
  pop r24
21
  pop r0
22
  out __SREG__,r0
23
  pop r0
24
  pop r1
25
  reti
26
27
__vector_2:
28
  push r1
29
  push r0
30
  in r0,__SREG__
31
  push r0
32
  clr __zero_reg__
33
  push r18
34
  push r19
35
  push r20
36
  push r21
37
  push r22
38
  push r23
39
  push r24
40
  push r25
41
  push r26
42
  push r27
43
  push r30
44
  push r31
45
  rcall foo_noinline
46
  pop r31
47
  pop r30
48
  pop r27
49
  pop r26
50
  pop r25
51
  pop r24
52
  pop r23
53
  pop r22
54
  pop r21
55
  pop r20
56
  pop r19
57
  pop r18
58
  pop r0
59
  out __SREG__,r0
60
  pop r0
61
  pop r1
62
  reti
63
64
  .comm  dummy,1,1

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Warum die o.g. Optimierung von GCC nicht durchgeführt wird — da müsst
> ihr die GCC-Entwickler fragen.

Ich könnte mir vorstellen, dass der GCC bei einer externen Funktion
grundsätzlich erstmal davon ausgeht, dass diese ggf. durch den Linker
ja noch ersetzt werden könnte.  Regulär geht das natürlich nur, wenn
sie als “weak” deklariert ist.  Vielleicht lässt sich die Wirkung
dieses Attributs (das ja eigentlich sonst nur dem Linker durchgereicht
werden muss) auch im Tree intern nicht mehr abbilden, sodass er an
dieser Stelle einfach nicht weiß, ob eine externe Funktion weak ist
oder nicht.

von Falk B. (falk)


Lesenswert?

@Johann L. (gjlayde) Benutzerseite

>Ansonsten gilt: Eine Funktion, die (im compilierten Code) aufgerufen
>wird, wird wie eine Black Box behandelt, d.h. alle call-clobbered
>Register sind als zerstört anzusehen.

Woher stammt eigentlich dieses seltsame Verhalten, dass VOR dem Aufruf 
von Funktionen die Register vorsorglich gesichert werden?
Wäre es denn nicht sinnvoller, die Sicherung der "beschmutzten" Register 
in die Funktion zu verlegen? Da entfällt nämlich auch das Problem der 
unnötigen Sicherung der Register in der ISR.

Schon klar, die Leute vom GCC werden sich schon was dabei gedacht haben, 
die Frage ist WAS?

von Peter II (Gast)


Lesenswert?

Falk Brunner schrieb:
> Woher stammt eigentlich dieses seltsame Verhalten, dass VOR dem Aufruf
> von Funktionen die Register vorsorglich gesichert werden?

werden sie ja nicht. Die ISR sicher ihre register die sie verwendet. Da 
sie nicht weiss welche sie verwendet sicher sie alle.

Nach deiner logic müsste man ja gar keine register sichern, wenn man 
keine funktion aufruft.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Falk Brunner schrieb:
> Woher stammt eigentlich dieses seltsame Verhalten, dass VOR dem Aufruf
> von Funktionen die Register vorsorglich gesichert werden?

Siehe ABI-Beschreibung (in der avr-libc-FAQ).

Es gibt halt Register, die sind “caller-saved”, und welche, die sind
“callee-saved”.  Erstgenannte müssen in einer ISR halt extra
gesichert werden, bevor eine Funktion gerufen werden kann.

Die Festlegung des ABIs ist reichlich alt (weshalb dummerweise auch
R0 und R1 darin eine Sonderrolle bekommen haben; sowas wie die
Multiplikationsbefehle gab es damals noch nicht).  Schätzungsweise
ist das immer eine Abwägung zwischen dem, dass der Aufgerufene sowieso
Arbeitsregister braucht (die Parameter fallen da am Ende auch mit rein),
und den auf den Stack zu sichernden Registern.  Wenn
du gar keine “caller-saved”-Register einrichtest, kann es gut sein,
dass der Aufgerufene Dinge auf den Stack sichert, die anschließend
vom Aufrufer weggeworfen werden, weil er sie gleich wieder als
Arbeitsregister überschreibt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Falk Brunner schrieb:
> Woher stammt eigentlich dieses seltsame Verhalten, dass VOR dem Aufruf
> von Funktionen die Register vorsorglich gesichert werden?

Grund ist das ABI und dort das Register-Layout:

http://gcc.gnu.org/wiki/avr-gcc#Register_Layout

> Schon klar, die Leute vom GCC werden sich schon was dabei gedacht haben,
> die Frage ist WAS?

Das ABI wiederum enstand als Co-Entwicklung zusammen mit dem AVR-Backend 
des GCC, damals (2000) durch Denis Chertykov.  Dabei hat der ziemlich 
viel mit unterschiedlichen Layouts rumexperimentiert — auch in welchen 
Registern Werte übergeben werden.  Das dabei gemachten Erkenntnisse 
flossen ins ABI ein, das bis heute i.W. unverändert ist.  Auch die 
Entscheidung, R0 als Scratch-Register zu verwenden und R1 als 
Zero-Register datiert in diese Zeit, zu der es — wie Jörg bereits 
bemerkte — noch keine MUL-Befehle mit den impliziten Registern R0 und R1 
gab.

> Wäre es denn nicht sinnvoller, die Sicherung der "beschmutzten" Register
> in die Funktion zu verlegen? Da entfällt nämlich auch das Problem der
> unnötigen Sicherung der Register in der ISR.

Ein Jeder-kümmert-sich-um-seinen-eigenen-Scheiß-ABI also...

Das würde bedeuten, daß es keine Call-Clobbered Register gibt und jede 
Funktion alle Register sichert, die sie anfasst. Das würde z.B. 
Leaf-Funktionen viel teurer machen. Diese erzeugen beim momentanen ABI 
nur dann PUSH/POP, wenn das Kontingent an Call-Clobbered Registern 
ausgeschöpft ist.  Für den Code von oben würde das lediglich bedeuten, 
daß sich die PUSH/POP-Orgie in den Callee verlagern würde — und damit 
bei jedem Aufruf (Laufzeit-)Overhead erzeugt, nicht nur einmalig beim 
Betreten/Verlassen der ISR.

Wenn du damit rumspielen magst, die Definition ist in 
CALL_USED_REGISTERS und überschreibbar in 
TARGET_CONDITIONAL_REGISTER_USAGE:

http://gcc.gnu.org/onlinedocs/gccint/Register-Basics.html#Register-Basics

Implementiert in avr.h bzw. avr.c:

http://gcc.gnu.org/viewcvs/gcc/trunk/gcc/config/avr/avr.h?view=markup#l171
http://gcc.gnu.org/viewcvs/gcc/trunk/gcc/config/avr/avr.c?view=markup

Allerdings ist nicht damit zu rechnen, daß nach Änderung korrekter Code 
erzeugt wird, denn die in Assembler stehenden Funktionen der libgcc 
und AVR-Libc kümmern sich natürlich nicht darum :-P

Weiters gibt es CALLER_SAVE_PROFITABLE:

http://gcc.gnu.org/onlinedocs/gccint/Caller-Saves.html#Caller-Saves

das im AVR-Backend nicht überschrieben wird. Allerdings liest man da:

>> [...] to determine whether it is worthwhile to consider placing
>> a pseudo-register in a call-clobbered hard register and saving
>> and restoring it around each function call. [...]

Das Register muß dazu auf dem Stack in einem eigenen Stackslot gesichert 
werden, was teuer ist.  Es in einem Call-Saved Register zu sichern wäre 
witzlos, denn dann könnte es direkt dort allokiert werden.

Meine Erfahrung mit diesem Caller-Saves ist nicht gut.  Falls es 
verwendet wird, liefert es nicht selten schlechteren Code, d.h. der Code 
wird dann mit -fno-caller-saves besser.

von Falk B. (falk)


Lesenswert?

Puhhh, da hab ich ja mal wieder ne Frage gestellt!
Danke für die Antworten, aber in irgendwelchen Compilern rumschrauben 
ist nun Weiß Gott nicht mein Ding. Bin doch nur ein kleiner Hardwerker 
;-)

Der avr gcc ist im Wesentlichen schon recht gut, nur in einigen Fällen 
produziert er halt etwas merkwürdigen Code, den der gelernte 
Assemblerprogrammierer mit seinem Tunnelblick für die optimal angepasste 
Lösung nicht versteht.

Mit etwas Distanz versteht aber sogar jemand wie ich, dass auch der 
beste Compiler ein Kompromiss ist aus generischen Programmustern und 
Konventionen und speziellen Optimierungen.

Wenn's halt WIRKLICH schnell sein muss, nimmt man halt ne komplette ISR 
in reinem Assembler. Oder einen 32 Bit ARM. Oder noch besser, ein FPGA. 
;-)

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.