Forum: Mikrocontroller und Digitale Elektronik ATTiny85: pgm_read_word() bläht Codegröße auf


von Sortland (Gast)


Lesenswert?

Hi,

ich habe eine Applikation, deren Codegröße bisher knapp unter der 
8K-Grenze war (8152 Bytes). Jetzt habe ich an 5 Stellen ein fehlendes 
pgm_read_word() eingefügt - und bin plötzlich 510 Bytes über der 
8K-Grenze!

Wenn ich mir ansehe, was hinter pgm_read_word() steckt, dann finde ich 
allerdings nur ein paar lausige Assemblerbefehle - keinesfalls genug, um 
für jeden Aufruf ca. 100 Bytes Code dazuzumogeln.

Hat jemand eine Idee, wo das herkommen könnte? Verursacht 
pgm_read_word() auf irgend welchen verschlungenen Pfaden noch 
zusätzlichen Code? Oder was sonst könnte das sein?

Danke!

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Sortland schrieb:
> Jetzt habe ich an 5 Stellen ein fehlendes pgm_read_word() eingefügt -
> und bin plötzlich 510 Bytes über der 8K-Grenze!
Was passiert, wenn du das nur an 1 Stelle einfügst? Oder an 10?

von Georg G. (df2au)


Lesenswert?

Warum vergleichst du nicht einfach die *.lss Files? Dann siehst du 
sofort, wo was dazu gekommen ist.

Auf Anhieb ist der Effekt nicht nachvollziehbar.

von Carl D. (jcw2)


Lesenswert?

Hast du schon die folgenden Optionen in Benutzung?

-mcall-prologues
Die optimiert die Push/Pop's falls du viele Funktionen hast, die nicht 
ge-inlined werden

-flto
Auf Compiler und Linker anzuwenden, wobei der Linker dann auch die
-O<dein-opt-Level> bekommen sollte. Wirkt wenn man mehrere Compile-Units 
sprich C-Files hat. Dann wird "über alles" optimiert.

von Mitlesa (Gast)


Lesenswert?

Georg G. schrieb:
> Auf Anhieb ist der Effekt nicht nachvollziehbar.

Ich würde das - ohne den Code angeschaut zu haben - so
interpretieren:

Der Compiler "sieht" die Anforderung, verursacht durch neue
Funtionsaufrufe, mehr Register zu sichern und wiederherzustellen
als vorher.

Bekanntlich geschieht das Sichern und Wiederherzustellen von
Registerinhalten (bei optimierendem Compiler) abhängig davon
welche Register nun wirklich verwendet werden.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Sortland schrieb:
> bin plötzlich 510 Bytes über der 8K-Grenze!
Lies da die ersten 5 Stunden:
Beitrag "C versus Assembler->Performance"
Da sind einige Tipps vergraben...

von Falk B. (falk)


Lesenswert?

@Sortland (Gast)

>8K-Grenze war (8152 Bytes). Jetzt habe ich an 5 Stellen ein fehlendes
>pgm_read_word() eingefügt - und bin plötzlich 510 Bytes über der
>8K-Grenze!

Klingt zuviel.

>Hat jemand eine Idee, wo das herkommen könnte? Verursacht
>pgm_read_word() auf irgend welchen verschlungenen Pfaden noch
>zusätzlichen Code?

Kann sein.

> Oder was sonst könnte das sein?

Möglicherweise wurden vorher ein paar Operationen wegoptimiert, weil sie 
nie im Programmablauf genutzt wurden. Mit pgm_read_byte() geht das 
anscheinend nicht mehr.

Poste deinen Quelltext als Anhang, dann kann man dir helfen.

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


Lesenswert?

Mitlesa schrieb:
> Der Compiler "sieht" die Anforderung, verursacht durch neue
> Funtionsaufrufe

Das sind gar keine Funktionsaufrufe, das sind ein paar einfache
Inline-Assembler-Anweisungen.  Expandiert durch den Präprozessor
(hier für einen ATmega256RFR2) kommt da so ein Kauderwelsch raus
wie der hier:
1
(__extension__({ __asm__ __volatile__ ( "movw  r0, %4\n\t" "movw r30, %A3\n\t" "sts %1, %C3\n\t" "sts %0, %2\n\t" "spm\n\t" "clr  r1\n\t" : : "i" (((uint16_t) &((*(volatile uint8_t *)((0x37) + 0x20))))), "i" (((uint16_t) &((*(volatile uint8_t *)((0x3B) + 0x20))))), "r" ((uint8_t)((1 << (0)))), "r" ((uint32_t)(address)), "r" ((uint16_t)(data)) : "r0", "r30", "r31" ); }));

Die 500 Bytes mehr müssen also woanders herkommen.  Schließlich werden
die gelesenen 5 Werte ja wohl nicht nur einfach weggeworfen, sondern
irgendwas passiert mit denen.  Vielleicht stand das, was da „passiert“,
ja vorher im Quellcode auch schon da, aber der Compiler konnte den
ganzen Firlefanz wegwerfen und gleich das Endergebnis hinschreiben,
da es dann konstant war?  Jetzt sind die Eingabedaten plötzlich für
den Compiler nicht mehr vorhersagbar, und er muss stattdessen die
komplette Rechnung einfügen … nur so als Schuss ins Blaue, ohne den
Code gesehen zu haben.

Edit: Falk hat die gleiche Idee gehabt. ;)

: Bearbeitet durch Moderator
von Sortland (Gast)


Lesenswert?

Die Änderung ist relativ simpel. Früher stand da:
1
speedCtr=speedTable[state.currIdx];

Jetzt ist da draus ein
1
speedCtr=pgm_read_word(&speedTable[state.currIdx]);

geworden. speedTable ist
1
PROGMEM const short speedTable[1900]={...

Unverändert im Code: speedCtr wird anschließend in einer ISR 
heruntergezählt. state.currIdx wird von außen (USB-Kommunikation) 
gesetzt.

Die vorherige Verwendung des Arrays ohne den Zugriff über 
pgm_read_word() war schlichtweg ein Bug, hier wurden auch immer 
Müllwerte nach speedCtr geschrieben. Den kompletten Code kann ich leider 
nicht posten, die Lizenz ist dafür zu restriktiv :-/

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Sortland schrieb:
> Den kompletten Code kann ich leider nicht posten
Dann musst du einfach mal das Assemblerlisting vorher-nachher 
vergleichen. Da siehst du recht schnell, wo die fast 10% "Zugewinn" 
herauskommen...

von Peter II (Gast)


Lesenswert?

Sortland schrieb:
> speedCtr=pgm_read_word(&speedTable[state.currIdx]);

wenn das ganze in einer ISR steht, kommen die ganzen Pop und Pusch dazu.

von Peter D. (peda)


Lesenswert?

Sortland schrieb:
> speedTable ist
> PROGMEM const short speedTable[1900]={...

Das sind ja schonmal 3,7kB Flash-Verbrauch, also fast 50%.
Kannst Du dafür keine Formel einsetzen?

von Carl D. (jcw2)


Lesenswert?

Probier doch einfach mal aus, was ich oben geschrieben hab. Das ist zwar 
erstmal keine "Codebereinungung", die du ja auch aus Lizenzgründen 
komplett selbst machen müsstest, hat aber im TransistorTester-Thread mal 
von 110% auf unter 100% Flash gebracht. Die Randbedingungen sind oben 
schon beschrieben, aber noch mal kurz:

Viele (nicht-ge-inline-de) Funktionen mit hoher Registerlast und damit 
vielen Push's am Anfang profitieren von -mcall_prologues. Ganz einfach 
anzuwenden und mit minimalen Overhead (wenige Takte pro Call).

LTO, bei vielen Compilation-Units (C-Files). Dazu müssen Compiler und 
Linker-Aufruf mit -flto Versehen werden und beide die selbe 
Optimierungsstufe bekommen, z.B. -Os.

von Karl H. (kbuchegg)


Lesenswert?

Peter II schrieb:
> Sortland schrieb:
>> speedCtr=pgm_read_word(&speedTable[state.currIdx]);
>
> wenn das ganze in einer ISR steht, kommen die ganzen Pop und Pusch dazu.

Sollte eigentlich nicht sein.
pgm_read_word wird per inline aufgelöst.

Wenn das zum Function-call-Register-saving führt, muss Johann wieder 
ran.

von Carl D. (jcw2)


Lesenswert?

Vermutlich war davor die nicht benutzte Tabelle auch nicht im Flash. Der 
GCC ist da sehr gut drin, unbenutztes einfach wegzulassen.

von Peter II (Gast)


Lesenswert?

Karl H. schrieb:
> Sollte eigentlich nicht sein.
> pgm_read_word wird per inline aufgelöst.
>
> Wenn das zum Function-call-Register-saving führt, muss Johann wieder
> ran.

ok, hatte nicht nachgeschaut. Dachte das ist eine normale Funktion aus 
der lib.

von Carl D. (jcw2)


Lesenswert?

> Wenn das zum Function-call-Register-saving führt, muss Johann wieder
ran.

Wenn die ISR die Register bisher nicht gebraucht hatte, die dieser 
inline-Code nun nutzt (z.B. Z-Reg für LPM), dann braucht's die 
zusätzlichen Push/Pop's. Nur 500 Byte werden das nicht so schnell 
werden.

von Karl H. (kbuchegg)


Lesenswert?

Carl D. schrieb:
>> Wenn das zum Function-call-Register-saving führt, muss Johann wieder
> ran.
>
> Wenn die ISR die Register bisher nicht gebraucht hatte, die dieser
> inline-Code nun nutzt (z.B. Z-Reg für LPM), dann braucht's die
> zusätzlichen Push/Pop's.

Klar brauchts es die. Aber nicht die Push/Pop Orgie aller Register, 
die ein Funktionsaufruf in einer ISR nach siech zieht.

> Nur 500 Byte werden das nicht so schnell
> werden.

Noch nicht mal, wenn alle Register gesichert werden.

Ich denke auch, dass der gcc da vorher Dinge wegoptimiert hat, was jetzt 
nicht mehr geht.

von Georg G. (df2au)


Lesenswert?

Karl H. schrieb:
> Ich denke auch, dass der gcc da vorher Dinge wegoptimiert hat, was jetzt
> nicht mehr geht.

Genau drum mein Vorschlag, die *.lss Files zu vergleichen. Das dauert 5 
Minuten und ist zielführender als viele Mutmassungen und Spekulationen.
Aber das muss der TO machen, der Code ist ja geheim.

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


Lesenswert?

Sortland schrieb:
> Die Änderung ist relativ simpel. Früher stand da:
>
>
1
speedCtr=speedTable[state.currIdx];
>
> Jetzt ist da draus ein
>
>
1
speedCtr=pgm_read_word(&speedTable[state.currIdx]);
>
> geworden. speedTable ist

Probier mal, ob du stattdessen (wie im Parallelthread vorgeschlagen)
__flash nutzen kannst.  U. U. kann der Compiler das dann besser
optimieren.

von Karl H. (kbuchegg)


Lesenswert?

Jörg W. schrieb:

> Probier mal, ob du stattdessen (wie im Parallelthread vorgeschlagen)
> __flash nutzen kannst.  U. U. kann der Compiler das dann besser
> optimieren.

Interessant.
Frage an Johann: Gibt es da ein Potential?

Wenn ja, dann wäre das ein ziemlich gutes Argument (neben das 
Typsicherheit, die __flash mit sich bringt)

von (prx) A. K. (prx)


Lesenswert?

Karl H. schrieb:
> Frage an Johann: Gibt es da ein Potential?

Mit fällt da nur ein Unterschied ein:

Mit __flash ist es aus Sicht des Compilers ein normaler Array-Zugriff 
auf ein konstantes Array. Ist dessen Inhalt bekannt könnte der Compiler 
bei konstantem Index den Wert direkt verwenden.

Bei den pgmspace Makros funktioniert das nicht. Der Compiler könnte zwar 
redundante Zugriffe bei gleichem Index rausoptimieren, aber der 
Zusammenhang zwischen Index und Wert geht verloren.

: Bearbeitet durch User
von Sortland (Gast)


Lesenswert?

So, mal Butter bei die Fische:

Wenn ich die Option -flto verwende, bekomme ich eine Fehlermeldung "cc1: 
error: LTO support has not been enabled in this configuration"

Ein .lss/.lst-File, mit dem ich irgend welche Codeunterschiede erkennen 
könnte, wird nicht erzeugt. Meine Buildumgebung ist ein avr-gcc mit 
Makefile. Wie komme ich an diese Files?

pgm_read_word() wird insgesamt drei mal verwendet, zwei mal davon in 
meiner ISR.

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


Lesenswert?

Sortland schrieb:
> Wie komme ich an diese Files?

avr-objdump -dS yourfile.elf > yourfile.lss

U. u. ist es aber einfacher, die Dinger ohne den eingesprenkelten
(und oft durch Inlining reichlich deplatziert wirkenden) Quellcode
zu lesen:

avr-objdump -d yourfile.elf > yourfile.lss

Oft auch recht hilfreich:

avr-nm -S --size-sort yourfile.elf > yourfile.sym

Durch das --size-sort landen die größten Objekte ganz am Ende.  Wenn
du die beiden vergleichst (vorher/nachher), solltest du also recht
schnell eine Idee bekommen, an welcher Stelle der Code explodiert ist.

: Bearbeitet durch Moderator
von Georg G. (df2au)


Lesenswert?

Sortland schrieb:
> Wie komme ich an diese Files?

Hast du Dr. Gurgel mal kontaktiert? Es gibt knapp 6000 Einträge zu "gcc 
.lss". Die Files stehen üblicherweise im Verzeichnis "default".

Beispiel:
## Build
all: $(TARGET) uart861.hex uart861.eep uart861.lss size

## Compile
uart861.o: ../uart861.c
  $(CC) $(INCLUDES) $(CFLAGS) -c  $<

##Link
$(TARGET): $(OBJECTS)
   $(CC) $(LDFLAGS) $(OBJECTS) $(LINKONLYOBJECTS) $(LIBDIRS) $(LIBS) -o 
$(TARGET)

%.hex: $(TARGET)
  avr-objcopy -O ihex $(HEX_FLASH_FLAGS)  $< $@

%.eep: $(TARGET)
  -avr-objcopy $(HEX_EEPROM_FLAGS) -O ihex $< $@ || exit 0

%.lss: $(TARGET)
  avr-objdump -h -S $< > $@

size: ${TARGET}
  @echo
  @avr-size -C --mcu=${MCU} ${TARGET}

: Bearbeitet durch User
von Sortland (Gast)


Lesenswert?

Jörg W. schrieb:
> avr-nm -S --size-sort yourfile.elf > yourfile.sym

OK, das ergebnis ist, dass eine Sektion __vector_10 (was auch immer das 
ist - meine ISR?) und <main> schlichtweg mehr Code enthalten. 
Vergleichen ist eher schwer, da verlassen mich meine Assemblerkenntnisse 
und ein Compare-Tool markiert dann fast alles als "changed".

von Falk B. (falk)


Lesenswert?

@ Sortland (Gast)

>OK, das ergebnis ist, dass eine Sektion __vector_10 (was auch immer das
>ist - meine ISR?)

Ja.

> und <main> schlichtweg mehr Code enthalten.

Mach mal einen Test. Lass mal zum Vergleich das PROGMEM und die pgm_read 
Funktionen weg, dann wird es ein normales Array im RAM. Der ist dann 
zwar zu klein, aber egal. Damit kann man sehen, ob das Problem damit 
zusammen hängt oder nicht. Wenn der Flashverbrauch nur geringfügig 
sinkt, liegt das Problem darin, dass in deiner früheren Version 
Programmteile wegoptimiert wurden.
1
const short speedTable[1900]={...
2
speedCtr=speedTable[state.currIdx];

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.