Forum: Mikrocontroller und Digitale Elektronik ARM Cortex & GCC: Worauf achten für "optimalen" Code?


von Ralf (Gast)


Lesenswert?

Hi,

ich bin nun von der 8051- in die ARM-Welt eingestiegen und verwende NxP 
Cortex-M0/M3 in Verbindung mit der LPCxpresso IDE von CodeRed und GCC.
IDE läuft, Debugging geht, soweit alles im Grünen.

Ich versuche nun im Selbststudium mit den Controllern fit zu werden, was 
auch klappt. Der UART läuft schon, der ADC wird grad zum Leben erweckt 
:)

Wo ich nun eher Anlaufschwierigkeiten habe ist der GCC, beispielsweise 
wie die Variablen bzgl. Speichermanagement gehandhabt werden, wie 
Parameterübergabe stattfindet, etc.
Ich komm leider momentan mit der in der LPCxpresso-IDE mitgelieferten 
GCC-Hilfe (noch) nicht zurecht, die Suchwörter spucken alles mögliche 
aus, nur aus meiner Sicht absolut nicht das was ich wissen will :(

Ein konkretes Beispiel: Ich hab die 8051er-Programme u.a. mit dem Keil 
C51 Compiler erstellt.
- Das Alignment spielte da keine Rolle, weil es ein 8-Bit-Controller 
ist. Wie ist das beim GCC & ARM? Wenn ich vier 8-Bit-Variablen (char) 
anlege, packt der GCC das automatisch in ein 32-Bit-Wort, oder verwendet 
er vier Mal ein 32-Bit-Wort? Wie kann ich das selber prüfen? Einfach mal 
ein paar Variablen anlegen und nach dem Build bei der Speicherangabe 
prüfen wieviel Bytes verbraten wurden? Ist das "aussagekräftig"?
Was ich bis jetzt in der Hilfe zum GCC rausgefunden habe, ist dass es 
beispielsweise beim Anlegen von structs nicht gepackt wird, d.h. im 
Speicher sind Lücken, wenn die struct nicht ein Vielfaches von 32-Bit 
verwendet. Das Packen kann durch ein Attribut bei der struct-Definition 
aktiviert werden.

- Der Keil C51-Compiler bzw. der Linker kann funktionsinterne Variablen 
überlagern, sodass Speicherplatz gespart wird. Allerdings geht das nur 
auf Sourcedatei-Ebene, d.h. die optimale Speicherauslastung hat man nur 
erreicht, wenn jede Funktion in einer eigenen Sourcedatei steht. Wie 
funktioniert dieser Mechanismus beim GCC?

- Beim Keil C51-Compiler wurden aufgrund der 8051-Architektur die 
Parameter in den Registern übergeben, auch hier war die Reihenfolge der 
Parameter bei der Übergabe wichtig, um optimalen Code zu erzeugen, da 
ansonsten zusätzlicher RAM für die Parameterübergabe verwendet wurde. 
Ich vermute beim GCC werden die Parameter über einen Stack übergeben, 
richtig? Wie lässt sich hier der Code optimieren? Wenn ich 
beispielsweise für eine Funktion vier Parameter habe, für die der Typ 
char ausreichend ist, werden dann vier 32-Bit-Wörter auf den Stack 
geschoben oder wird's gepackt? Bzw. wenn's nicht gepackt wird, wäre doch 
die Verwendung eines structs als Parameter geschickter, oder?

Ich versuche eben, meine Erfahrung mit dem 8051/Keil auf den ARM/GCC zu 
"portieren", um die Lernkurve bzgl. des Compilers zu optimieren :)
Wenn also jemand nützliche Links hat, bitte hier posten. Ich werde das 
gleiche tun, vielleicht ergibt sich ja eine ordentliche Linksammlung.

Ralf

von (prx) A. K. (prx)


Lesenswert?

Ralf schrieb:

> ist. Wie ist das beim GCC & ARM? Wenn ich vier 8-Bit-Variablen (char)
> anlege, packt der GCC das automatisch in ein 32-Bit-Wort, oder verwendet
> er vier Mal ein 32-Bit-Wort?

Register: jede einzeln in ein Register. Deklaration lokaler Variablen 
mit weniger als 32 Bits ist ineffizient.

Speicher: in 8 Bits gespeichert.

> Wie kann ich das selber prüfen?

Code ansehen, Adressen von Variablen im Dump suchen oder ggf.ausgeben.

> Was ich bis jetzt in der Hilfe zum GCC rausgefunden habe, ist dass es
> beispielsweise beim Anlegen von structs nicht gepackt wird, d.h. im
> Speicher sind Lücken,

Es wird nicht gepackt, es sei denn das wird ausdrücklich gewünscht. 
Gepackt ist recht ineffizient.

> - Der Keil C51-Compiler bzw. der Linker kann funktionsinterne Variablen
> überlagern, sodass Speicherplatz gespart wird.

51er speichern lokale Daten statisch im RAM, da sie mit einem Stack für 
Daten schlecht umgehen können. ARMs verwenden einen Stack und damit löst 
sich das Problem in Luft auf.

> Ich vermute beim GCC werden die Parameter über einen Stack übergeben,

Nein, Register. Nur wenns zu viele sind, was seltten ist.

von nicht Gast (Gast)


Lesenswert?

A. K. schrieb:
>> ist. Wie ist das beim GCC & ARM? Wenn ich vier 8-Bit-Variablen (char)
>> anlege, packt der GCC das automatisch in ein 32-Bit-Wort, oder verwendet
>> er vier Mal ein 32-Bit-Wort?
>
> Register: jede einzeln in ein Register. Deklaration lokaler Variablen
> mit weniger als 32 Bits ist ineffizient.

das is so nicht richtig. Die Register sind immer 32 bit, es macht aber 
keinen unterschied, da man die Registeroperationen auch auf Bytes 
anwenden kann

Die erste Variable wir immer mit dword aligment angelegt.
1
struct {
2
 uint8_t a[3],
3
 uint32_t b[1]
4
} c;
in diesem Beispiel hat c eine größe von 8, weil eine Lücke zwischen a 
und b ist.

packed sollte man nur machen wenn man es wirklich braucht, z.B. bei 
Frames die über einen Bus kamen. Lieber beim erstellen selbst darauf 
auchten das keine Lücken entstehen.

von Alex E. (tecnologic) Benutzerseite


Lesenswert?

Hi zusammen,

Zum Attribut "packed" hätte ich ne Frage

struct{
uint8_t a;
uint8_t b;
uint8_t c;
uint8_t d;
} foo;

ist diese Struct ohne packed auch nur 4 Byte lang oder 16?
Also packt der GCC Variablen die in ein Dword passen zusammen rein und 
fängt erst beim Nächsten an, wenn er eine Var nicht mehr in das erste 
Dword bekommt?

MfG

Tec

von nicht Gast (Gast)


Lesenswert?

4 byte. Hier muss die CPU sowieso einen byte weisen zugriff machen.

von Alex E. (tecnologic) Benutzerseite


Lesenswert?

Danke dachte ich brauche auch hier für packed.

von Ralf (Gast)


Lesenswert?

@Prx & nicht Gast:
Danke für die Erklärungen.

>> Es wird nicht gepackt, es sei denn das wird ausdrücklich gewünscht.
>> Gepackt ist recht ineffizient.
> in diesem Beispiel hat c eine größe von 8, weil eine Lücke zwischen a
> und b ist.
Passt jetzt aber nicht zur der Antwort auf tecnologics Frage:
>> ist diese Struct ohne packed auch nur 4 Byte lang oder 16?
> 4 byte. Hier muss die CPU sowieso einen byte weisen zugriff machen.
Oder ist im Beispiel von nicht Gast die Lücke in der struct weil der 
uint32_t Member "geschickterweise" auf ein Word-Boundary gelegt wird?

Ralf

von Alex E. (tecnologic) Benutzerseite


Lesenswert?

HI Ralf,

so verstehe ich das auch, der uin32_t passt nicht mehr in das Dword mit 
den 3 uint8_t deshalb beginnt der Compiler ein neues Dword, so erreicht 
er ja 32bit aligned Zugriff, und muss sich die Var nicht aus 2 Blöcken 
zusammen suchen. Darauf wollte ich ja auch hinnaus, weil ich mir da nie 
sicher war.
hab das dann immer in der Form gemacht.

struct{
uint32_t bFlag:1;
uint32_t reseve:31;
} foo __attribute_((_packed_));

so ist der Compiler gezwungen. ich werde das aber noch mal ohne Packed 
probieren.

MfG

Tec

von (prx) A. K. (prx)


Lesenswert?

nicht Gast schrieb:

> das is so nicht richtig. Die Register sind immer 32 bit, es macht aber
> keinen unterschied, da man die Registeroperationen auch auf Bytes
> anwenden kann

Das ist so nicht richtig. Erstens kann man nur Lade/Speicheoperationen 
auf 8/16 Bits anwenden, nicht aber ALU Operationen. Zweitens stehen 
(deshalb) bestimmte Compiler-Konventionen im Weg.
1
short reg16(short a, short b)
2
{
3
    short x = a + b;
4
    return x;
5
}
6
int reg32(int a, int b)
7
{
8
    int x = a + b;
9
    return x;
10
}
wird zu
1
reg16:  add  r0, r1, r0
2
        mov  r0, r0, asl #16
3
        mov  r0, r0, asr #16
4
        bx   lr
5
reg32:  add  r0, r1, r0
6
        bx   lr

von Peter D. (peda)


Lesenswert?

A. K. schrieb:
> 51er speichern lokale Daten statisch im RAM, da sie mit einem Stack für
> Daten schlecht umgehen können.

Nö, der 8051 kann auch mit dem Stack arbeiten.
Allerdings ist es erheblich effizienter, direkt auf den SRAM 
zuzugreifen. Es muß kein Stackframe angelegt werden und viele Befehle 
können im SRAM gemacht werden (MOV, INC, DEC, DJNZ, ANL, XRL, ..).
Außerdem wird selten ein RTOS verwendet, daher lassen sich lokale 
Variablen überlagern.

A. K. schrieb:
> ARMs verwenden einen Stack und damit löst
> sich das Problem in Luft auf.

Sie können nichts direkt im SRAM ausführen (RISC), daher ist es fast 
egal, ob aus dem SRAM laden oder PUSH/POP.


Peter

von (prx) A. K. (prx)


Lesenswert?

Peter Dannegger schrieb:

> Nö, der 8051 kann auch mit dem Stack arbeiten.
> Allerdings ist es erheblich effizienter, direkt auf den SRAM
> zuzugreifen.

Ich habe nichts anderes behauptet, mit schlecht=ineffizient.

von (prx) A. K. (prx)


Lesenswert?

Peter Dannegger schrieb:

>> ARMs verwenden einen Stack und damit löst
>> sich das Problem in Luft auf.
>
> Sie können nichts direkt im SRAM ausführen (RISC), daher ist es fast
> egal, ob aus dem SRAM laden oder PUSH/POP.

Es ging hier um die Platzfrage.

Wenn lokale Variablen statisch angelegt werden, dann ist es aus 
Platzgründen sinnvoll, dass der Compiler etwas Aufwand treibt, um solche 
Variablen von sich nicht gegenseitig aufrufenden Funktionen überlagern 
zu können.

Wenn lokale Variablen in Registern bzw. auf einem Stack angelegt werden, 
dann löst sich diese Platzfrage ohne jede Optimierung ganz von allein, 
da diese Überlagerung eine implizite Eigenschaft des Stacks ist.

von Stellaris (Gast)


Angehängte Dateien:

Lesenswert?

Hier eine App Note von TI zu den Cortex M3 von TI:

Optimizing Code Performance and Size for Stellaris® Microcontrollers

Das dürfte auch für deine NXP gelten.

Gruss

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.