Forum: Mikrocontroller und Digitale Elektronik Unterschied in Codegröße zwischen CodeVision und WinAVR


von Steffen Graap (Gast)


Lesenswert?

Hallo

Ich benutze beruflich wie auch privat den CodeVision C-Compiler. 
Prozesoren: ATtiny26, ATMega32, ATMega128.
Nun möchte ich auf Eclipse mit WinAVR/AVR-GCC als Compiler umsteigen. 
Dabei interessiert mich hauptsächlich die bessere IDE von Eclipse und 
die möglichkeiten Plugins einzubinden wie z.B. CVS oder ähnliches.

Zu Testzwecken habe ich ein bestehendes Project, welches mit CV 
geschrieben wurde auf WINAVR angepasst (Änderung von Interruptaufrufe, 
Includes, Bitvarialen, Flashkonstanten, EEPromzugriffe).

Nach dem Compilieren mußte ich aber feststellen, das der Code um ~18% 
größer geworden ist.
CodeVision, Ooptimierung max.Size, 1805 Bytes
WINAVR mit -Os, 2132 Bytes
Auch die Optionen -O1 bis -O3 (ohne -Os) haben keine Verringerung der 
Codegröße gebracht.
Da das Projekt auf einem Tiny26 mit 2k Flash laufen soll, hätte ich 
schon ein Problem.
Im privaten Sector würde ich einfach einen größeren Prozessor nehmen. 
Aber im beruflichen Sector, wo auch Stückzahlen eine Rolle spielen, 
erzeugt jeder Cent der für einen größeren Prozessor ausgegeben werden 
muß entsprechende Mehrkosten.

Ist der Compiler beim optimieren einfach schlechter oder warum kommt es 
zu diesem  Ergebnis?
Gibt es weitere Compiler die mit Eclipse zusammen arbeiten, aber besser 
optimieren?

Gruß Steffen

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


Lesenswert?

Steffen Graap wrote:

> Ist der Compiler beim optimieren einfach schlechter oder warum kommt es
> zu diesem  Ergebnis?

Kann man, ohne den Code zu sehen, nichts dazu sagen.

von Steffen Graap (Gast)


Angehängte Dateien:

Lesenswert?

Hallo Jörg

Danke für die schnelle Antwort
Anbei das gezipte Eclipse-Projekt

Gruß Steffen

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


Lesenswert?

Kannst du mal die Codegrößen der einzelnen Funktionen von Codevision
aufschreiben?  Die größte Funktion ist INT0_vect, aber wenn ich mir
den generierten Assemblercode ansehe, finde ich da kaum noch
Optimierungspotenzial.  Gerade in Bereichen wie dem Aufblähen von
8-bit-Zahlen auf 16 bits (integer promotion) ist der GCC hier besser,
als ich erwartet hätte (das ist sonst seine schwache Seite).

Wenn man es mit -mint8 compiliert, passt es gerade so in die 2 KiB
rein.  Das könnte für eine derartige Applikation sogar legal sein,
aber schick ist es nicht.  Mich würde mal interessieren, wo CV hier
so viel besser ist, dann guck ich nochmal weiter.

Achte bitte auf eine einheitliche Groß-/Kleinschreibung.  Wenn die
Datei boot.h heißt, sollte dann nicht #include "Boot.h" stehen.  Zum
Glück war's nur eine Stelle.

von Andreas K. (a-k)


Lesenswert?

Es könnte hilfreich sein, in der ISR einige der "static" Vars vorneweg 
zu laden und hinten wieder wegzuspeichern. Ziemlich viel Platz wird mit 
den vielen LDS/STS verbraten, Register sind billiger.

Oder: Die statischen und externen Variablen alle miteinander in eine 
struct packen und per Pointer adressieren. LDD ist nämlich kürzer als 
LDS.

von Steffen Graap (Gast)


Lesenswert?

Im Anhang habe ich mal das Projekt in CodeVision-Style angehängt. In der 
bm8.lst kann man gut die Zuornung von assembler zu C-Code sehen.
Die Größe der Funktion ist INT0_vect in den beiden Compiler wie folgt
CV: 410 Byte (von 0x131 - 0x2CB)
WINAVR: 634 (608) laut *.s Datei

Ich denke eine wichtige Sache ist, das in CV Bitvariablen, soweit wie 
möglich (bis 104 Bit), in die Prozessorregister gelegt werden. Das hat 
den Vorteil, das man mit SET/CLT & BLD ein Bit setzen/löschen kann 
(3Byte) und mit SBIC/SBIS die Abfrage machen kann ohne vorhergehenden 
Ladebefehl.

Das zweite was ich gefunden habe ist so etwas:

lds r24,u8Adresse
cpi r24,lo8(4)
brsh .L57
lds r24,u8Adresse         (nicht nötig, kostet 4 Byte)
clr r25
subi r24,lo8(-(au8Prog))
sbci r25,hi8(-(au8Prog))

Die option -mint8 ist ein guter Vorschlag. Ich hatte mich schon 
gewundert warum bei 8bit Variablen immer mit 16bit gerechnet wurde.

@ Andreas Kaiser

Danke für deine Vorschläge, werde sie berücksichtigen

Gruß Steffen

von Andreas K. (a-k)


Lesenswert?

In welchem Anhang?

von Steffen Graap (Gast)


Angehängte Dateien:

Lesenswert?

sorry

das Laden der Variablen in Register funktioniert aber nur in einer 
Interrupt-Routine. Hier wird die Abarbeitung ja nicht durch andere 
Interrupts unterbrochen, sodas die Register überschriben werden könnten.
Welche Register sollte man für soetwas benutzen? Hat der Compiler 
bevorzugte Register?
Sollte ein Compiler so etwas nicht von alleine machen (Optimierung)?


Gruß Steffen

von Andreas K. (a-k)


Lesenswert?

Richtig, es ging mir nur um die ISR. Aber das ist der mit Abstand 
grösste Brocken.

Er sollte es alleine machen, ja, aber manchmal hilft es, dem Compiler 
entgegenzukommen. Ausprobieren. Mit Registern meinte ich eher, Variablen 
die von der Arbeitsweise her "static" sein müssen, vorneweg in "auto" 
Variablen zu laden, nicht in explizite Register.

Wäre es nicht ausgerechnet ein schon angestaubter Tiny26, liessen sich 
auch die bei neueren AVRs vorhandenen 3 GIOR Bytes im I/O-Space 
platzsparend einsetzen. Atmel dürfte die als Analogon zum Bitspace der 
8051 verstanden haben, für überwiegend bitadressierten Kram sind Bytes 
im I/O-Bereich ideal.

von Andreas K. (a-k)


Lesenswert?

Was die Optimierung von in Speicher plazierten Variablen sehr effektiv 
aushebeln kann ist, ist die Gefahr von Aliasing. Vielleicht ist etwas 
gewonnen, wenn du GCC davon überzeugen kann, dass diesbezüglich von 
pu8Bytezeiger keine Gefahr ausgeht.

von Andreas K. (a-k)


Lesenswert?

Grund für den bei CV erheblich kürzeren Code ist eine 
Optimierungstechnik, die im GCC mangels Sinn (Speed) auf 
Workstations/Servern nicht enthalten ist: Auslagerung kurzer 
Codesequenzen in kleinste Unterprogramme (bm8.lst: OPTIMIZER ADDED 
SUBROUTINE).

Im Zusammenhang mit GCC ist mir dieser Schritt beim Microchip C30 
begegnet (Procedural Abstraction), dort aber als proprietäre Erweiterung 
in einem separaten Compiler-Pass realisiert.

Beispiel:

  ...
  variable += 1;  // 5 Worte
  ...
  variable += 1;
  ...
  variable += 1;
  ...
  variable += 1;
  ...

ist kürzer, wenn realisert als

  void incr(void) { variable += 1; }

  ...
  incr(); // 1 Wort
  ...
  incr();
  ...
  incr();
  ...
  incr();
  ...

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


Lesenswert?

Steffen Graap wrote:

> Das zweite was ich gefunden habe ist so etwas:
>
> lds r24,u8Adresse
> cpi r24,lo8(4)
> brsh .L57
> lds r24,u8Adresse         (nicht nötig, kostet 4 Byte)
> clr r25
> subi r24,lo8(-(au8Prog))
> sbci r25,hi8(-(au8Prog))

Das hast du ihm aber extra so gesagt, indem du einen ganzen Haufen
Variablen als volatile deklariert hast.  Damit zwingst du den
Compiler, sie immer aus dem Speicher zu lesen und sie jedesmal
wieder zurück zu schreiben.

Ich überblicke die ganze state machine da nicht, aber ich vermute
mal ganz stark, dass keine einzige davon volatile sein muss.  Wenn
überhaupt, dann würde wahrscheinlich ein Synchronisationsflag als
volatile genügen.  Leider ist das alles recht unübersichtlich, wenn
man nur mal so draufguckt.

Ich sehe gerade, dass der CV einige der Variablen in globale Register
gepackt hat.  Das kann er, da er ja immer alle Dateien zusammen als
eine einzige C-Datei compiliert und daher nie von externen Moduln
ausgehen muss.  Das könnte man dem GCC natürlich auch noch nahelegen.
Allerdings sind deine Headerdateien ziemlich durcheinander und nicht
sauber nach Definition und Deklaration getrennt, daher gelingt mir
das jetzt nicht auf Anhieb.

Deine EEPROM-Zugriffe stimmen für den GCC übrigens so noch nicht,
da musst du explizit eeprom_write_...() aufrufen.

von Andreas K. (a-k)


Lesenswert?

Yep, die volatiles hatte ich glatt übersehen. Aber da passt der Trick 
mit dem pointer-to-struct rein. Denn damit lässt sich diese 
optimierungsunfreundliche Eigenschaft recht elegant innerhalb der ISR 
umgehen, und da ist das ja ohnehin nicht relevant. D.h. wenn der Pointer 
auf die Daten nicht volatile ist, kann optimiert werden. Und ausserhalb 
der ISR ist es weiterhin volatile.

von Steffen Graap (Gast)


Lesenswert?

Das die ganzen Variablen als volatile deklariert sind, hat den Grund, 
das im Tutorial für WINAVR steht, das Variablen die innerhalb und 
außerhalb von ISR benutzt werden, so deklariert werden sollten. In CV 
brauchte ich dies nicht machen.

Mit der Statemachine wird einfach ein Bus (Selectrix) ausgewertet, mit 
jedem Bustakt wird auf das nächste Bit geschaltet.

Das GCC mit den Prozessorregistern recht sparsam umgeht ist mir auch 
schon aufgefellen. In CV wird R0 und R1 für die Assemblerbefehler, die 
Register R2 - R15 für Bitvariabeln (Anzahl einstellbar), alle übrigen 
(außer X, Y und Z, für die globalen Variablen (automatisch oder manuel 
zugewiesen) genutzt.

Das mit den EEPromzugriffen ist mir bekannt, habe ich aber der 
Einfachheit halber weg gelassen, da der Compiler nicht gemeckert hat. 
Dies würde den Code aber nochals aufblähen.

Gruß Steffen

von Andreas K. (a-k)


Lesenswert?

Steffen Graap wrote:

> In CV brauchte ich dies nicht machen.

Mag sein, aber GCC ist nicht für Microcontroller optimiert, sondern für 
High-End Maschinen in denen zeilen/blockübergreifende Optimierung 
wichtig ist. Und es ist auch nicht GCC oder Jörg, der "volatile" 
fordert, sondern der C Standard seit 20 Jahren.

> Das GCC mit den Prozessorregistern recht sparsam umgeht

Er hat bei "volatile" einfach keine Chance, denn das ist letztlich 
nichts anderes also die Aussage "führe bitte keine Registeroptimierung 
durch". Er tut damit nur was du ihm sagst.

Also:

  volatile struct S {
     int var1;
     int var2;
  } globale_variablen;

und dann in der ISR:

  struct S *gvars = (struct S *) &globale_variablen;
  ... gvars->var ...

und der Spuk dürfte vorbei sein. In der ISR selbst besteht für 
"volatile" kein Grund, solange du auf nested interrupts verzichtest.

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


Lesenswert?

Naja, ist ziemlich unsauber.  Ich würde eher dafür plädieren, volatile
nur dort zu benutzen, wo es wirklich notwendig ist.  Allerdings ist
diese Abwägung zuweilen ziemlich schwierig.  Normalerweise sollte es
komplett genügen, eine Art Semaphore volatile zu haben und den Rest
nicht.

von Andreas K. (a-k)


Lesenswert?

> Naja, ist ziemlich unsauber.

Es ist geringfügig umständlich in der Handhabung und vielleicht nicht 
allzu schön, aber m.E. durchaus sauber, was den Standard angeht.

> Normalerweise sollte es komplett genügen, eine Art Semaphore volatile
> zu haben und den Rest nicht.

Hilft in diesem Fall rein garnicht. Denn an der Optimierung von 
Variablen ändert keine Semaphore irgendwas. Nur die Problematik bei 
nicht-atomarem Zugriff lässt sich per Sema erledigen.

Und was ISRs angeht, hilft sie auch da nicht, denn was machst du in der 
ISR wenn die Sema grad blockiert ist?

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


Lesenswert?

Andreas Kaiser wrote:

>> Naja, ist ziemlich unsauber.

> Es ist geringfügig umständlich in der Handhabung und vielleicht nicht
> allzu schön, aber m.E. durchaus sauber, was den Standard angeht.

Meine Lesart des Standards ist, dass das Ergebnis des ,,Wegcastens''
eines type qualifiers nicht definiert ist.

>> Normalerweise sollte es komplett genügen, eine Art Semaphore volatile
>> zu haben und den Rest nicht.

> Und was ISRs angeht, hilft sie auch da nicht, denn was machst du in der
> ISR wenn die Sema grad blockiert ist?

Ich meinte mehr so eine Art Flag, das im main context gepollt werden
kann und sicherstellt, dass man auf konsistente Daten zugreift.
Eigentlich braucht man volatile doch nur für Dinge, bei denen kein
sequence point dazwischen liegt, also das typische Flag, auf das man
in main() pollt.

U. U. ist gerade für so eine einfache Applikation eine "memory barrier"
sinnvoller zu handhaben als eine Unmenge an volatile-Variablen.  Die
"memory barrier" wird implementiert mit:
1
asm volatile(:::"memory");

Sie zwingt den Compiler, danach alle Werte, die aus dem Speicher
gelesen worden sind, neu zu evaluieren.  Leider kann man sie
meiner Meinung nach nicht auf Register anwenden (falls man globale
Registervariablen benutzt).  Ich bin mir nicht ganz sicher, ob ein
vergleichbarer Trick für solche Variablen benutzbar wäre:
1
asm volatile(:::"r2");

von Andreas K. (a-k)


Lesenswert?

>
1
asm volatile(:::"memory");

Das ist nun aber sehr compilerspezifisch. Und für Ersatz von "volatile" 
Vars einigermassen umständlich und fehlerträchtig, denn das muss man 
dann im gesamten Hauptprogramm an den Zugriffen auf solche Variablen 
unterbringen und es setzt gewissen Vorstellungen von der Arbeitsweise 
von Compilern voraus. Zudem wirft es alle speicherbezogenen 
Optimierungen über Bord, also auch die harmlosen Variablen.

Wenn wegcasten von qualifiern nicht definiert ist, wie gibt man dann 
einen String aus (d.h. übergibt ihn an eine Funktion, die "char *" 
erwartet)? Immer erst in eine lokale Variable umkopieren? Ist mir noch 
nirgends begegnet. Dafür aber jede Menge casts, um "const" loszuwerden 
weil die aufgerufene Funktion irgendwann in grauer Vorzeit ohne "const" 
deklariert wurde.

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


Lesenswert?

Andreas Kaiser wrote:

> Wenn wegcasten von qualifiern nicht definiert ist, wie gibt man dann
> einen String aus (d.h. übergibt ihn an eine Funktion, die "char *"
> erwartet)? Immer erst in eine lokale Variable umkopieren?

Ja, das ist der einzig saubere Weg, den ich im Standard sehe.  Das
ist auch nicht verwunderlich, ist ja schließlich ein Standard, und
der soll die Eckpunkte markieren.  Letztlich deutet die Situation,
die du beschreibst, ja auf einen Designfehler hin (gerufene Funktion
ändert zwar die Daten nicht, wurde aber nicht sauber dahingehend
deklariert, sodass man mit einem Hack arbeiten muss).  Dein Cast
tötet den Compilertest aber auch für die Fälle, in denen es wirklich
kracht, d. h. die gerufene Funktion ist als non-const char * deklariert,
wird mittels typecast aus einem const char * gefüttert, hackt aber
anschließend tatsächlich auf dem String herum (wozu sie gemäß ihrer
Deklaration berechtigt ist) -> peng!

Da das ,,Wegcasten'' des qualifiers aber ein Designfehler ist, würde
ich dies niemandem für ein Neudesign empfehlen.

von Steffen Graap (Gast)


Lesenswert?

Danke für eure Antworten.
Ich habe die volatile Deklaration wieder entfernt, da ich sie nicht 
benötige. Da ich meine C Kenntnisse mit CodeVision entwickelt habe, bin 
ich nicht so fit in ANSI-C. Aufgrund des Hinnweis im Tutorial habe ich 
die deklaration einfach eingefügt ohne weiter darüber nach zu Denken.
Mittlerer Weile bin ich bei einer Codegröße angelangt, die 10% größer 
als bei CodeVision ist. Für mich eine vertretbare Größe.

Über die Firma in der ich arbeite werden wir mal versuchen ob Pavel 
(Entwickler von CodeVison) eine Komandozeilen-Version seines Compilers 
mitanbietet, die dann in die Eclipseumgebeung eingebunden werden kann. 
Das währe für mich das Optimum (kleiner Code, max. Flexibilität der 
IDE).

Gruß Steffen

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.