PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Codegröße verleinern



Amri
03.07.2005, 15:34
Hallo,
ich habe folgendes Problem in Codevision AVR: Mein Programm hat 1060 Worte, der Controller aber nur 1024 Worte Flash. Gibt es irgendwelche Tricks, wie ich den Code kleiner kriegen kann, also zum Beispiel "Ersatzbefehle", die weniger Platz brauchen oder bestimmte Compilereinstellungen?
Bisher habe ich den Compiler auf Optimize for size eingstellt und den Quellcode teilweise etwas verkürzt, aber es sind halt immer noch 36 Worte zuviel.

M@nni
03.07.2005, 16:37
Hallo,

im Zweifelsfall kann man sicherlich die eine oder andere C-Funktion durch eine Assemblerfunktion ersetzen, je nachdem, wie gut oder wie schlecht der C-Compiler übersetzt. Besser ist es natürlich, wenn man direkt in den C-Funktionen optimieren kann.

Aber ohne Deine Software zu sehen, lassen sich schlecht konkrete Vorschläge machen. Zeig doch mal Dein Programm, dann kann man Dir sicherlich weiter helfen.

Gruß, M@nni

Amri
03.07.2005, 17:06
Ich habe das Programm jetzt auf 1017 Worte runtergekriegt. Das habe ich hauptsächlich durch Variablendefinitionen hinbekommen: Je nachdem ob man eine Variable lokal oder global definiert, kann man bis zu etwa 20 (!) Worte pro Variable sparen!
Im Moment ist mein Problem also gelöst, falls aber noch jemand Tipps in dieser Art hat kann er sie gerne hier posten.

SprinterSB
08.07.2005, 17:23
Was gcc nicht so gut schafft, ist den Zugriff auf globale (ausserhalb jeder Funktion) definierte Variablen zu optimieren.

So führt


typedef unsigned char byte;

byte var2;

void foo1()
{
var2++;
if (var2 > 10)
var2 = 1;
}

zu folgendem asm Code (snip aus dem lst-File):


0000005c <foo1>:
5c: 80 91 63 00 lds r24, 0x0063
60: 8f 5f subi r24, 0xFF ; 255
62: 80 93 63 00 sts 0x0063, r24
66: 8b 30 cpi r24, 0x0B ; 11
68: 18 f0 brcs .+6 ; 0x70
6a: 81 e0 ldi r24, 0x01 ; 1
6c: 80 93 63 00 sts 0x0063, r24
70: 08 95 ret

(Unser var2 wurde nach 0x0063 lokatiert).
Der Code umfasst 11 Worte (2x11 Byte).
Der Schreib-Zugriff in 62 ist überflüssig. Var2 ist ein normales, nicht-flüchtiges Objekt (nicht volatile).

Den Zugriff umgeht man am einfachsten, indem man für var2 eine lokate Variable anlegt. Wird optimiert compiliert (zb -Os), dann legt gcc locals in Register, wenn noch Platz ist:


void foo2()
{
byte var = var2;

var++;
if (var > 10)
var = 1;

var2 = var;
}

Das führt zu Code, der nur noch 9 Einheiten groß ist:


00000072 <foo2>:
72: 80 91 63 00 lds r24, 0x0063
76: 8f 5f subi r24, 0xFF ; 255
78: 8b 30 cpi r24, 0x0B ; 11
7a: 08 f0 brcs .+2 ; 0x7e
7c: 81 e0 ldi r24, 0x01 ; 1
7e: 80 93 63 00 sts 0x0063, r24
82: 08 95 ret

Nachteil ist natürlich, daß der Quellcode unschöner wird.

Eine weitere Reduktion der Codegröße kann man durch indirekten Zugriff erreichen. Alle Variablen, auf die oft (in Bezug auf Code) zugegriffen wird, legt man in eine global bekannte Struktur (global_t), erzeugt eine Instanz dieser Struktur (globals) und legt die Adresse von globals ins Y-Register (g):


// Struktur für oft gebrauchte Variablen
typedef struct
{
byte var0;
byte var1;
byte var2;
...
} global_t;

global_t globals;

// g (Y-Reg) hält die Adresse von globals
register global_t *g asm ("r28");

void foo3()
{
byte var = g->var2;

var++;
if (var > 10)
var = 1;

g->var2 = var;
}

void main()
{
...
g = &globals;
...
}

Das resultiert in folgendem Code. g wird in main() mit 0x0066 vorgeladen. Y+2 adressiert dann g->var2 (globals.var2).


00000084 <foo3>:
84: 8a 81 ldd r24, Y+2 ; 0x02
86: 8f 5f subi r24, 0xFF ; 255
88: 8b 30 cpi r24, 0x0B ; 11
8a: 08 f0 brcs .+2 ; 0x8e
8c: 81 e0 ldi r24, 0x01 ; 1
8e: 8a 83 std Y+2, r24 ; 0x02
90: 08 95 ret

000000c8 <main>:
...
d0: c6 e6 ldi r28, 0x66 ; 102
d2: d0 e0 ldi r29, 0x00 ; 0
...


Unsere Funktion foo3() hat nur noch eine Größe von 7, im Vergleich zu 11 in foo1()!

Nicht zu erwähnen, daß das auch Fallstricke hat!
Das Y-Register wird von gcc als Framepointer verwendet. Standardmässig ist in gcc -fomit-framepointer aktiviert, so daß dieser möglichst immer eliminiert wird. Ist eine Funktion jedoch zu komplex oder kann der Framepointer nicht eliminiert werden, dann führt unsere Technik zu falschem Code (siehe unten).

Daher muss man, wenn man diese Technik anwenden weil, auf einiges achten:

Die Struktur und die Deklaration von g als asm("r28") muss in ALLEN Quellen, die zum Projekt gehören, bekannt sein. Denn Y-Reg (r28:r29) darf nicht verändert werden. Das gilt auch für Quellen, die globals nicht benutzen!


Auf diese Weise lassen sich maximal 64 Byte sinnvoll verwalten, denn der ldd-Befehl des AVR lässt nur Offsets von 0 bis 63 zu.


Um sicher zu gehen, daß kein falscher Code erzeugt wurde, macht man ein grep über den generierten asm-Code:
Die ersten beiden Fundstellen setzen den Stackpointer, die dritte ist Teil unserer Zuweisung g = &globals. Findet grep mehr, dann muss man die Quelle solange vereinfachen, bis r28:r28 nicht mehr verwendet wird und nur noch diese 3 Fundstellen verbleiben.
Assenbler-Listings erstellt man übrigens mit -save-temps oder mit -S anstatt -c.



> grep "r28" *.s
ldi r28,lo8(__stack - 0)
out __SP_L__,r28
ldi r28,lo8(globals) ; g, ; 8 *movhi/4 [length =

Beherzigt man dies, dann funktioniert das prima. \:D/
Und vor allem auf kleinen Controllern passt deutlich mehr rein. Wo was zu holen ist, sieht man mit einem Blick ins list-File.
Ich verwende diese Technik bei kleinen AVRs wie dem 2313 problemlos.

Hier noch ein Beispiel, daß zu falschem Code führt.
Verantwortlich ist das volatile auf eine lokale Variable, so daß sie nicht in einem Register leben kann.


void foo4()
{
volatile byte dummy;

byte var = g->var2;

var++;

if (var > 10)
var = 1;

g->var2 = var;

dummy = 0x55;
}

ergibt folgenden Horror-Code:


00000092 <foo4>:
92: cf 93 push r28
94: df 93 push r29
96: cd b7 in r28, 0x3d ; 61
98: de b7 in r29, 0x3e ; 62
9a: 21 97 sbiw r28, 0x01 ; 1
9c: 0f b6 in r0, 0x3f ; 63
9e: f8 94 cli
a0: de bf out 0x3e, r29 ; 62
a2: 0f be out 0x3f, r0 ; 63
a4: cd bf out 0x3d, r28 ; 61
a6: 8a 81 ldd r24, Y+2 ; 0x02
a8: 8f 5f subi r24, 0xFF ; 255
aa: 8b 30 cpi r24, 0x0B ; 11
ac: 08 f0 brcs .+2 ; 0xb0
ae: 81 e0 ldi r24, 0x01 ; 1
b0: 8a 83 std Y+2, r24 ; 0x02
b2: 85 e5 ldi r24, 0x55 ; 85
b4: 89 83 std Y+1, r24 ; 0x01
b6: 21 96 adiw r28, 0x01 ; 1
b8: 0f b6 in r0, 0x3f ; 63
ba: f8 94 cli
bc: de bf out 0x3e, r29 ; 62
be: 0f be out 0x3f, r0 ; 63
c0: cd bf out 0x3d, r28 ; 61
c2: df 91 pop r29
c4: cf 91 pop r28
c6: 08 95 ret

Dieser Code ist falsch, denn Y kann nicht zugleich den Framepointer und den Zeiger auf unser globals enthalten.