- SF800 Solar Speicher Tutorial         
Ergebnis 1 bis 4 von 4

Thema: Codegröße verleinern

  1. #1
    Erfahrener Benutzer Fleißiges Mitglied
    Registriert seit
    03.11.2004
    Beiträge
    156

    Codegröße verleinern

    Anzeige

    Powerstation Test
    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.

  2. #2
    Neuer Benutzer Öfters hier
    Registriert seit
    01.06.2005
    Beiträge
    13
    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

  3. #3
    Erfahrener Benutzer Fleißiges Mitglied
    Registriert seit
    03.11.2004
    Beiträge
    156
    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.

  4. #4
    Erfahrener Benutzer Robotik Einstein Avatar von SprinterSB
    Registriert seit
    09.06.2005
    Ort
    An der Saar
    Beiträge
    2.802
    Was GCC nicht so gut schafft, ist den Zugriff auf globale (ausserhalb jeder Funktion) definierte Variablen zu optimieren.

    So führt
    Code:
    typedef unsigned char byte;
    
    byte var2;
    
    void foo1()
    {
    	var2++;
    	if (var2 > 10)
    		var2 = 1;
    }
    zu folgendem asm Code (snip aus dem lst-File):
    Code:
    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:
    Code:
    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:
    Code:
    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):
    Code:
    // 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).
    Code:
    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.

    Code:
    > 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. \/
    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.
    Code:
    void foo4()
    {
    	volatile byte dummy;
    
    	byte var = g->var2;
    	
    	var++;
    	
    	if (var > 10)
    		var = 1;
    		
    	g->var2 = var;
    
    	dummy = 0x55;
    }
    ergibt folgenden Horror-Code:
    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.

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •  

LiFePO4 Speicher Test