Forum: Compiler & IDEs "Include-Libs" - Teufelswerk?


von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Hallo zusammen,

ich habe da als Programmierer einen Gewissenskonflikt. Doch zunächst 
will ich mal die Situation schildern.

Ich habe einen Bootloader geschrieben, um einen ATmega328 mittels 
W5100-Ethernet-Controller über TCP/IP zu flashen. Ziel war es, dass der 
Bootloader nicht mehr als 2KB groß wird.

Also habe ich mich im Internet um Quellen zum W5100 bemüht, diese 
übernommen, stark abgewandelt und möglichst noch optimiert. Soweit so 
gut.

Herausgekommen ist:

w5100.c - Chip-spezifische Funktionen (static) plus Interface-Funktionen
          wie ip_init(), tcp_listen(), tcp_recv() usw.
w5100.h - Deklaration der Interface-Funktionen

ipbootloader.c - Der eigentliche Bootloader

Das alles zusammengelinkt erzeugt ein Programm von 1994 Bytes. Ich bin 
also knapp unter der Grenze von 2KB, habe aber keine Spielräume bzgl. 
Erweiterungen.

Ich dachte mir: Was passiert eigentlich, wenn ich die 
Interface-Funktionen auch static deklariere und in ipbootloader.c nicht 
w5100.h per include einfüge, sondern w5100.c?

Gemacht, getan: Die Programmgröße reduzierte sich auf 1570 Bytes. Ist ja 
auch klar, denn nun kann der Compiler die Interface-Funktionen (da nun 
static) auch inlinen. Aber dass der Unterschied so groß ist?

Ich bin eigentlich ein Freund des modularen Programmierens, aber die 
Ersparnis war so enorm, dass ich w5100.c als optionale "Include-Lib" 
umgebaut habe.

Grob umrissen:

ipbootloader.c:
1
  #define W5100_INCLUDE_LIB
2
  #include "w5100.c"

ws5100.c:
1
  #ifdef W5100_INCLUDE_LIB
2
  #define STATIC static
3
  #else
4
  #define STATIC
5
  #endif
6
7
  ...
8
9
  STATIC uint8_t recv(....)
10
  {
11
     ...
12
  }

Mit diesem Konstrukt kann man w5100.c einmal klassisch zum Hauptprogramm 
dazu linken oder halt einfach "includen". Im ersten Fall steht dann wie 
gewohnt:
1
  #include "w5100.h"

Im zweiten Fall:
1
  #define W5100_INCLUDE_LIB
2
  #include "w5100.c"

... und man kann sich über das kleinere (und wohl auch performantere) 
Programm freuen.

Ich weiß aber, dass ein paar Hartgesottene hier prinzipielle Regeln 
haben, nämlich u.a.:

 - "Includiere niemals eine C-Datei, immer nur H-Datei"
 - "Definiere niemals in H-Dateien Funktionen"

Daran halte ich mich normalerweise auch. Aber muss man sich deshalb auf 
einem Mikrocontroller mit schlechter Programmgröße und ineffektiverem 
Code zufriedengeben?

: Bearbeitet durch Moderator
von Yalu X. (yalu) (Moderator)


Lesenswert?

Kann es sein, dass in w5100.c nicht benutzte Funktionen enthalten sind,
die zusätzlichen Flash-Speicher belegen? Deklarierst du sie als static
und includest sie direkt, werden sie vom Compiler wegoptimiert.

Mit den entsprechenden Optionen (die ich gerade nicht im Kopf habe)
kannst du den Compiler bzw. den Linker anweisen, jede Funktion in ein
eigenes Segment zu schreiben und unbenutzte Segmente wegzulassen. Dann
sollten auch bei modularer Programmierung unbenutzte Funktionen
wegoptimiert werden.

: Bearbeitet durch Moderator
von Dr. Sommer (Gast)


Lesenswert?

Frank M. schrieb:
> Aber muss man sich deshalb auf einem Mikrocontroller mit schlechter
> Programmgröße und ineffektiverem Code zufriedengeben?
Man kann auch einen aktuellen GCC verwenden und mit "-flto" kompilen & 
linken, dann hat man die gleichen Vorteile auch wenn man seperate .c(pp) 
Dateien verwendet.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Yalu X. schrieb:
> Kann es sein, dass in w5100.c nicht benutzte Funktionen enthalten sind,
> die zusätzlichen Flash-Speicher belegen? Deklarierst du sie als static
> und includest sie direkt, werden sie vom Compiler wegoptimiert.

Tatsächlich ist w5100.c möglichst allgemein gehalten, da ich sie als Lib 
weiterverwenden will. Sie enthält tatsächlich auch noch Funktionen für 
UDP, die für den Bootloader nicht benötigt werden. Ich habe daher den 
eigentlichen Source über eine w5100config.h konfigurierbar gemacht, so 
dass mit
1
#define W5100_NEED_UDP    0

diese Funktionen ausgeblendet werden.

Das funktioniert auch perfekt, denn sonst würde der gcc meckern und 
mitteilen, dass eine static-Funktion nicht benutzt wird. Den Gegencheck 
habe ich mit
1
#define W5100_NEED_UDP    1

auch schon gemacht (gcc trällert los), denn das war auch meine erste 
Idee. ;-)

Es sieht tatsächlich so aus, als ob das Inlinen wirklich unheimlich viel 
bringt. Und es wäre dumm, das nicht auszunutzen.

von Dr. Sommer (Gast)


Lesenswert?

Yalu X. schrieb:
> Mit den entsprechenden Optionen (die ich gerade nicht im Kopf habe)
> kannst du den Compiler bzw. den Linker anweisen, jede Funktion in ein
> eigenes Segment zu schreiben und unbenutzte Segmente wegzulassen.
Kompilieren+linken mit -ffunction-sections -fdata-sections und linken 
mit -Wl,--gc-sections , grad gant vergessen das noch mit zu posten

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Dr. Sommer schrieb:
> Man kann auch einen aktuellen GCC verwenden und mit "-flto" kompilen &
> linken, dann hat man die gleichen Vorteile auch wenn man seperate .c(pp)
> Dateien verwendet.

Das wäre eine Alternative. Ich verwende den gcc 4.7.2, der sollte 
halbwegs aktuell sein. Ich werde das mal ausprobieren, auch wenn sich 
bei mit bei Verwendung von gcc-spezifischen Optionen immer die 
Nackenhaare aufstellen.

Ich bin ein defensiv eingestellter Programmierer und hab es gern immer 
möglichst "kompatibel". Die Verwendung als "Include-Lib" ist nicht 
abhängig von der gcc-Version.

von Fred (Gast)


Lesenswert?

Die andere Verwendung auch nicht.

Codegröße ist immer compilerabhängig. Die zählt also nicht für einen 
maximal defensiven, portabel programmierenden Entwickler wie dich.

von Masl (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Man kann auch einen aktuellen GCC verwenden und mit "-flto" kompilen &
> linken, dann hat man die gleichen Vorteile auch wenn man seperate .c(pp)
> Dateien verwendet.

Grundsätzlich finde ich LTO sehr praktisch. ALlerdings habe ich mir 
damit auch schonmal ins Knie geschossen, deswegen bin ich da vorsichtig 
mittlerweile.

Für den TO dürfte die Lösung die du weiter unten ja auch genannt hast 
zielführender sein, also -ffunction-sections und -fdata-sections 
verwenden.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Dr. Sommer schrieb:
> Yalu X. schrieb:
>> Mit den entsprechenden Optionen (die ich gerade nicht im Kopf habe)
>> kannst du den Compiler bzw. den Linker anweisen, jede Funktion in ein
>> eigenes Segment zu schreiben und unbenutzte Segmente wegzulassen.
> Kompilieren+linken mit -ffunction-sections -fdata-sections und linken
> mit -Wl,--gc-sections , grad gant vergessen das noch mit zu posten

Danke, aber ich bin mir sicher, dass es keine unbenutzten Funktionen 
gibt. Ich achte immer penibel auf Warnungen. Wird eine static-Funktion 
nicht verwendet, sagt der gcc das ja auch.

Also daran liegt es nicht. Ich suche auch nicht nach Schwachstellen im 
Code (ich werde diesen sowieso hier in einem gesonderten Artikel 
veröffentlichen), sondern nach einer mindestens genauso guten Methode, 
modular zu programmieren und trotzdem dieselbe Programmgröße zu 
erreichen wie bei einer Include-Lib.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Masl schrieb:
> Für den TO dürfte die Lösung die du weiter unten ja auch genannt hast
> zielführender sein, also -ffunction-sections und -fdata-sections
> verwenden.

Nein. Damit blende ich lediglich unbenutzte Funktionen aus. Die habe ich 
aber gar nicht.

von Dr. Sommer (Gast)


Lesenswert?

Frank M. schrieb:
> auch wenn sich bei mit bei Verwendung von gcc-spezifischen Optionen
> immer die Nackenhaare aufstellen.

Wie viele andere AVR C-Compiler gibt es denn noch? Für die musst du dann 
halt deren entsprechende Optionen verwenden.

Frank M. schrieb:
> Die Verwendung als "Include-Lib" ist nicht abhängig von der gcc-Version.

Wer sich weigert neue Software zu installieren ist halt selber schuld 
dann und muss mit einem größeren Programm leben - ist ja nicht so dass 
es nicht funktioniert. Neuere Versionen können vermutlich auch die 
"normalen"  Optimierungen -O2 etc besser. Lieber eine nicht-antike 
Compilerversion verlangen als hässlichen Code zu schreiben...

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Gerade getetstet:

-flto als Compiler- und Linker-Option:

Ergebnis: Größe = 3134 Bytes, also nochmal rund 1k größer und nicht 
kleiner.

Dann noch getestet:

Zusätzlich zu "-flto" noch Compiler- und Linker-Optionen:

-ffunction-sections
-fdata-sections

und als Linker-Option:

-Wl,--gc-sections

Ergebnis: Größe = 3134 Bytes, also unverändert zu oben und absolut 
suboptimal.

hier der Output:
1
rm -rf ipbootloader.o w5100.o  ipbootloader.elf dep/* ipbootloader.hex ipbootloader.eep ipbootloader.lss ipbootloader.map
2
Build succeeded with 0 Warnings...
3
avr-gcc  -mmcu=atmega328p -Wall -gdwarf-2 -std=gnu99                -flto     -DF_CPU=16000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -MD -MP -MT ipbootloader.o -MF dep/ipbootloader.o.d  -c  ../ipbootloader.c
4
avr-gcc  -mmcu=atmega328p -Wall -gdwarf-2 -std=gnu99                -flto     -DF_CPU=16000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -MD -MP -MT w5100.o -MF dep/w5100.o.d  -c  ../w5100.c
5
avr-gcc -mmcu=atmega328p -Wl,--section-start=.text=0x7800 -flto  -Wl,-Map=ipbootloader.map ipbootloader.o w5100.o     -o ipbootloader.elf
6
avr-objcopy -O ihex -R .eeprom -R .fuse -R .lock -R .signature  ipbootloader.elf ipbootloader.hex
7
avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" --change-section-lma .eeprom=0 --no-change-warnings -O ihex ipbootloader.elf ipbootloader.eep || exit 0
8
avr-objdump -h -S ipbootloader.elf > ipbootloader.lss
9
10
AVR Memory Usage
11
----------------
12
Device: atmega328p
13
14
Program:    3134 bytes (9.6% Full)
15
(.text + .data + .bootloader)
16
17
Data:         26 bytes (1.3% Full)
18
(.data + .bss + .noinit)
19
20
Build succeeded with 0 Warnings...

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Dr. Sommer schrieb:
> Wer sich weigert neue Software zu installieren ist halt selber schuld
> dann und muss mit einem größeren Programm leben - ist ja nicht so dass
> es nicht funktioniert. Neuere Versionen können vermutlich auch die
> "normalen"  Optimierungen -O2 etc besser. Lieber eine nicht-antike
> Compilerversion verlangen als hässlichen Code zu schreiben...

gcc4.7.2 ist antik?!? Das glaubst Du doch selbst nicht.

von Oliver S. (oliverso)


Lesenswert?

Frank M. schrieb:
> Ich weiß aber, dass ein paar Hartgesottene hier prinzipielle Regeln
> haben, nämlich u.a.:
>
>  - "Includiere niemals eine C-Datei, immer nur H-Datei"
>  - "Definiere niemals in H-Dateien Funktionen"


Die einzig wahre Pauschalaussage ist, daß Pauschalaussagen immer 
irgendwo falsch sind.

Man darfselbstverständlich alles, wenn es sinnvoll ist, und man weiß was 
man tut. Bei dir ist der Ansatz sinvoll, und du weisst, was du tust. 
Alles in Ordnung.

Oliver

von Masl (Gast)


Lesenswert?

Frank M. schrieb:
> Danke, aber ich bin mir sicher, dass es keine unbenutzten Funktionen
> gibt. Ich achte immer penibel auf Warnungen. Wird eine static-Funktion
> nicht verwendet, sagt der gcc das ja auch.

Bei den vorgeschlagenen Compiler-Schalter geht es nicht um 
static-Funktionen. Werden solche nicht verwendet landen sie eh nicht im 
Code, da der Compiler, durch das static, sichergehen kann dass diese 
auch nicht von extern aufgerufen werden sollen.

Die Compiler-Schalter sorgen eher dafür, dass deine 
Interface-Funktionen, also alles was nicht static ist, bei Nichtgebrauch 
wegoptimiert werden können.
(Genauer: sie werden dann nicht dazu gelinkt. Die kleinste Einheit mit 
der der Linker Arbeiten kann sind Sektionen. Standardmäsig entspricht 
ein c-File einer Sektion. Somit werden entweder alle oder garkeine 
Funktionen eines c-Files gelinkt. Legst du aber alle Funktionen in 
eigene Sektionen kann der Linker unbenutzte Interfaces einfach 
weglassen.)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Frank M. schrieb:
> hier der Output:

Sorry, das war der Lauf mit lediglich "-flto". Hier der finale mit allen 
Optionen, der aber keine Besserung bringt:
1
avr-gcc  -mmcu=atmega328p -Wall -gdwarf-2 -std=gnu99                -flto       -DF_CPU=16000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -ffunction-sections  -fdata-sections  -MD -MP -MT ipbootloader.o -MF dep/ipbootloader.o.
2
d  -c  ../ipbootloader.c
3
4
avr-gcc  -mmcu=atmega328p -Wall -gdwarf-2 -std=gnu99                -flto       -DF_CPU=16000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -ffunction-sections  -fdata-sections  -MD -MP -MT w5100.o -MF dep/w5100.o.d  -c  ../w510
5
0.c
6
7
avr-gcc -mmcu=atmega328p -Wl,--section-start=.text=0x7800 -flto  -ffunction-sections  -fdata-sections  -Wl,--gc-sections  -Wl,-Map=ipbootloader.map ipbootloader.o w5100.o     -o ipbootloader.elf
8
avr-objcopy -O ihex -R .eeprom -R .fuse -R .lock -R .signature  ipbootloader.elf ipbootloader.hex
9
avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" --change-section-lma .eeprom=0 --no-change-warnings -O ihex ipbootloader.elf ipbootloader.eep || exit 0
10
avr-objdump -h -S ipbootloader.elf > ipbootloader.lss
11
12
AVR Memory Usage
13
----------------
14
Device: atmega328p
15
16
Program:    3134 bytes (9.6% Full)
17
(.text + .data + .bootloader)
18
19
Data:         26 bytes (1.3% Full)
20
(.data + .bss + .noinit)
21
22
23
Build succeeded with 0 Warnings...

von Masl (Gast)


Lesenswert?

Frank M. schrieb:
> -flto als Compiler- und Linker-Option:
>
> Ergebnis: Größe = 3134 Bytes, also nochmal rund 1k größer und nicht
> kleiner.
>
> Dann noch getestet:
>
> Zusätzlich zu "-flto" noch Compiler- und Linker-Optionen:
>
> -ffunction-sections
> -fdata-sections

Bei Verwendung von LTO musst du dem Linker den Optimierungsschalter 
mitgeben (also -Os oder -O3).
Bei LTO ist nämlich der Linker für das Optimieren des Codes 
verantwortlich. Fehlt dort das Flag ist das Resultat, wie von dir 
beobachtet, sogar schlechter.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Masl schrieb:

> Die Compiler-Schalter sorgen eher dafür, dass deine
> Interface-Funktionen, also alles was nicht static ist, bei Nichtgebrauch
> wegoptimiert werden können.

Das weiß ich doch! Aber da ich alle Interface-Funktionen mit dem Makro 
STATIC im Falle der Include-Lib ja definitiv static mache, bekomme ich 
das direkt mit, wenn eine (static) Funktion unbenutzt bleibt.

Verstanden?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Masl schrieb:
> Bei Verwendung von LTO musst du dem Linker den Optimierungsschalter
> mitgeben (also -Os oder -O3).

-Os ist bei mir selbstverständlich, siehe geposteten Output.

Leute, behandelt mich bitte nicht als Anfänger. ;-)

von Masl (Gast)


Lesenswert?

Frank M. schrieb:
> -Os ist bei mir selbstverständlich, siehe geposteten Output.

Aber nur beim Compileraufruf, nicht beim Linkeraufruf ;-)

von Masl (Gast)


Lesenswert?

Frank M. schrieb:
> Das weiß ich doch! Aber da ich alle Interface-Funktionen mit dem Makro
> STATIC im Falle der Include-Lib ja definitiv static mache, bekomme ich
> das direkt mit, wenn eine (static) Funktion unbenutzt bleibt.
>
> Verstanden?

Achso meinst du das. Ja ok, macht Sinn.
Dann kann man nur annehmen dass der Compiler dadurch aggresiver 
Optimieren kann als im Falle der "echten" Interface-Funktionen.

Dann müsste LTO ein mindestens gleich-gutes, wahrscheinlich besseres 
Ergebnis bringen.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Masl schrieb:
> Frank M. schrieb:
>> -Os ist bei mir selbstverständlich, siehe geposteten Output.
>
> Aber nur beim Compileraufruf, nicht beim Linkeraufruf ;-)

Wow! Das hats gebracht! Mir war noch nie bewusst, dass -Os auch vom 
Linker ausgewertet werden könnte!

Tatsächlich kommt dann exakt dasselbe Ergebnis wie bei Verwendung von 
Include-Libs heraus:
1
AVR Memory Usage
2
----------------
3
Device: atmega328p
4
5
Program:    1570 bytes (4.8% Full)
6
(.text + .data + .bootloader)
7
8
Data:         26 bytes (1.3% Full)
9
(.data + .bss + .noinit)

Ich bedanke mich bei allen für ihre Beteiligung. Ich werde zukünftig 
-flto und -Os als Compiler- und Linkeroption verwenden.

Ich habe lediglich noch eine Frage an Dich:

Wie kam der Knieschuss mit -flto zustande? Möchte da ja nur ungern in 
dieselbe Falle tappen.

: Bearbeitet durch Moderator
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Frank M. schrieb:
> Ich bedanke mich bei allen für ihre Beteiligung. Ich werde zukünftig
> -flto und -Os als Compiler- und Linkeroption verwenden.
>
> ...
>
> Wie kam der Knieschuss mit -flto zustande? Möchte da ja nur ungern in
> dieselbe Falle tappen.

LTO ist keine Optimierung des Linkers, es wird lediglich zur Linkzeit 
nochmals der Compiler aufgerufen, und zwar mit allen Objekten. Die 
Optionen werden jedoch über die Kommandozeile übergeben.

Die mit LTO übersetzten Objekte enthalten i.W. den AST (abstract syntax 
tree) zu einem frühen zeitpunkt des Compilevorgangs, also vor Inlining 
von Funktionen und anderen Optimierungen.  Beim LTO-Lauf extrahiert der 
Compiler die LTO-Info und hat dann die globale Information über alle 
Module /

Funktionen. Der Assembler-Code in den .text-Sections ist dann Makulatur; 
der Compiler interessiert sich nicht dafür.

Um die Aufrufe zu sehen, kann man z.B. -v -Wl,-v angeben.

Das Ergebnis von LTO sollte ähnlich zu dem sein, wenn man alle C-Quellen 
beim Compilieren angibt, denn auch dann hat der Compiler die globale 
Sicht, d.h. sowas wie

gcc a.c b.c c.c -o <optionen> abc.elf

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Die mit LTO übersetzten Objekte enthalten i.W. den AST (abstract syntax
> tree) zu einem frühen zeitpunkt des Compilevorgangs, also vor Inlining
> von Funktionen und anderen Optimierungen.  Beim LTO-Lauf extrahiert der
> Compiler die LTO-Info und hat dann die globale Information über alle
> Module /

Danke für die Hintergrundinfos. Für den Laien kann man also sagen: Der 
gcc schmeisst alle Module in einen Topf und betrachtet sie wie eine 
große C-Datei.

Aber leider beantwortet das noch nicht die Frage nach dem "Knieschuss".

Masl schrieb oben, dass er sich mit -flto mal heftig ins Knie geschossen 
hat.

Ich würde einfach gern die Gefahr kennen ;-)

von Masl (Gast)


Lesenswert?

Mein Knieschuss, hehe :-)

Ich wollte Funktionen für den Zugriff auf Portpins anlegen, ähnlich den 
digitalWrite aus den Arduino Libs. Ich habe dabei mit sehr vielen 
Compiler- und Linker Flags herumgespielt, da die Arduino-Lösung ja 
bekanntlich sehr unperformant ist.

Hauptproblem ist ja, der Compiler kann schlecht optimieren. Auch kann er 
nicht ohne weiteres sbi- und cbi - Befehle erzeugen.

Gegeben:

Dio.h
1
typedef enum
2
{
3
  DIO_CHANNEL_A0 = 0u,
4
  DIO_CHANNEL_A1,
5
  ...
6
  ...
7
}
8
9
void Dio_WriteChannel(Dio_channel_t channel, boolean value);

Dio.c
1
volatile uint8_t * const ports[] = { &PORTA, &PORTB, &PORTC, &PORTD};
2
const uint8_t masks[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
3
4
#define Dio_GetPinFromChannel(channel)        channel & 0x07
5
#define Dio_GetPortFromChannel(channel)        channel >> 3u;
6
7
void Dio_WriteChannel(Dio_channel_t channel, boolean value)
8
{
9
  uint8_t pin;
10
  uint8_t port;
11
12
  pin = Dio_GetPinFromChannel(channel);
13
  port = Dio_GetPortFromChannel(channel);
14
15
  if (value)
16
    *(ports[port]) |= masks[pin];
17
  else
18
    *(ports[port]) &= ~masks[pin];
19
}

Aufruf in main.c:
1
Dio_WriteChannel(DIO_CHANNEL_A1, 1);
Wie zu erwarten war wird hässlicher, aufgeblähter Code erzeugt. Anstatt 
einen Takt braucht das Bit-Setzen ein Vielfaches.

Dann das Ganze mit LTO probiert und den Assemblercode begutachtet.
Wow, der Funktionsaufruf in main resultiert tatsächlich in einem 
einzigen (!) sbi - Befehl!
Ich hab noch einige Tests gemacht und verglichen, auch mit inline und 
always-inline (da gibts auch Knieschüsse ;-)) und war mit dem LTO 
Ergebnis sehr zufrieden. Konnte keinen Fall nachstellen wo es nicht 
klappt.

...bis zur Praxis :-)

Gegeben sei beispielsweise ein Modul was ein Schieberegister per 
Pinwackeln anspricht.
Um das Modul universell zu halten wurde ein leicht objektorientierter 
Ansatz gewählt in dem jedes Schieberegister durch eine Konfiguration 
beschrieben wird:
1
typedef struct
2
{
3
    Dio_channel_t enablePin;
4
  Dio_channel_t clockPin;
5
  Dio_channel_t dataPin;
6
  uint8_t size;
7
}
8
latchConfig_t;

main.c
1
extern latchConfig_t myConfig[NUMBER_OF_LATCHES];
2
...
3
latch_write(&myConfig[1], 0xA5);

Und da schepperts dann. Durch das Ablegen der Channelnummern in einer 
Variable, die auch noch in einem anderen c-File liegt (extern) und nicht 
const ist hab ich dem Compiler wieder jegliche Möglichkeit zur 
Optimierung genommen. Da hilft auch kein LTO, das kann er nicht auf ein 
sbi runterbrechen. Das erzeugt in jedem Fall mehrere Aufrufe zu meiner 
Dio_WriteChannel Funktion. Und das will man nicht haben.

Deswegen sollte man bei aller Euphorie nicht vergessen was man macht und 
wo die Grenzen liegen :-)

PS: ich hab leider immer noch keine Performante Lösung für den AVR 
gefunden mit dem ich meine Kanalnummern irgendwo wegspeichern kann.

PPS: nein Peter, sbit.h kann dies auch nicht :-)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Masl schrieb:
> Und da schepperts dann. Durch das Ablegen der Channelnummern in einer
> Variable, die auch noch in einem anderen c-File liegt (extern) und nicht
> const ist hab ich dem Compiler wieder jegliche Möglichkeit zur
> Optimierung genommen.

Danke für die ausführliche Beschreibung, werde Dein Posting aber 
bestimmt noch 2- bis 3-mal lesen müssen, bis ich das eigentliche Problem 
bis ins Detail verstanden habe. Wahrscheinlich habe ich es erst dann 
kapiert, wenn ich mal selbst in so eine Situation komme. ;-)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Oliver S. schrieb:
> Man darfselbstverständlich alles, wenn es sinnvoll ist, und man weiß was
> man tut. Bei dir ist der Ansatz sinvoll, und du weisst, was du tust.
> Alles in Ordnung.

Ich danke auch Dir für Deinen Zuspruch, ich werde da zukünftig bei 
anderen Problemen daran denken.

Aber konkret hier hat die Option -flto dann doch den größeren Charme ;-)

von Karl H. (kbuchegg)


Lesenswert?

Frank M. schrieb:

> Danke für die ausführliche Beschreibung, werde Dein Posting aber
> bestimmt noch 2- bis 3-mal lesen müssen, bis ich das eigentliche Problem
> bis ins Detail verstanden habe. Wahrscheinlich habe ich es erst dann
> kapiert, wenn ich mal selbst in so eine Situation komme. ;-)

Sein Problem war:

Er hat angenommen, dass er ein wunderbares System gefunden hat, dass ihm 
spätestens bim Linken alles wieder ins Lot rückt und die Portzugriffe 
auf die Low-Level Bitbefehle runteroptimiert.
Er hat aber übersehen, dass dieses in seinem Fall prinzipiell nicht 
möglich ist.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Karl Heinz schrieb:
> Er hat angenommen, dass er ein wunderbares System gefunden hat, dass ihm
> spätestens bim Linken alles wieder ins Lot rückt und die Portzugriffe
> auf die Low-Level Bitbefehle runteroptimiert.
> Er hat aber übersehen, dass dieses in seinem Fall prinzipiell nicht
> möglich ist.

Genau das "Prinzip", warum es nicht klappen kann, war mir nach dem 
ersten Lesedurchgang nicht klar. Mittlerweile jedoch schon.

Danke.

von Masl (Gast)


Lesenswert?

Karl Heinz schrieb:
> Sein Problem war:
>
> Er hat angenommen, dass er ein wunderbares System gefunden hat, dass ihm
> spätestens bim Linken alles wieder ins Lot rückt und die Portzugriffe
> auf die Low-Level Bitbefehle runteroptimiert.
> Er hat aber übersehen, dass dieses in seinem Fall prinzipiell nicht
> möglich ist.

Hab auch mit const usw. noch rumgespielt und damit zumindest obiges 
Problem für genau diesen Fall lösen können.
Allerdings war mir das Ganze dann zu unsicher und ich habs bleiben 
lassen.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> LTO ist keine Optimierung des Linkers, es wird lediglich zur Linkzeit
> nochmals der Compiler aufgerufen, und zwar mit allen Objekten. Die
> Optionen werden jedoch über die Kommandozeile übergeben.

Jetzt bin ich doch nochmal ins Grübeln gekommen...

Ich habe verstanden, dass der Linker schlussendlich den gcc nochmal 
aufruft über alle C-Module, damit das mit dem LTO auch klappen kann. 
Aber woher kennt der gcc zu diesem Zeitpunkt die vormals benutzten 
Compiler-Optionen wie zum Beispiel:

  ... -gdwarf-2 -std=gnu99 -DF_CPU=16000000UL ....

Die muss der gcc beim ersten Aufruf ja irgendwo mitgespeichert haben, 
damit der durch den Linker aufgerufene "Rundumschlag-gcc" sie am Ende 
auch verwenden kann?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Nein, gespeichert wird da nix.

Aus technischer Sicht gibt es ein neues Frontend, also praktisch eine 
neue Sprache (lto). C-Programme werden vom cc1 übersetzt, C++ Programme 
von cc1plus, Assembler-Programme von as und lto-Programme eben von lto1.

lto wird produziert von cc1 bzw. cc1plus, die den bereits 
präprozessierten Code erhalten. lto ist quasi eine Byte-Code, der nach 
dem Präprozessing und nach dem C/C++ Parsing erstellt wird. 
Insbesondere braucht lto1 nicht mehr die Syntax der Eingabe zu checken; 
das ist alles schon passiert.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> lto wird produziert von cc1 bzw. cc1plus, die den bereits
> präprozessierten Code erhalten. lto ist quasi eine Byte-Code, der /nach/
> dem Präprozessing und nach dem C/C++ Parsing erstellt wird.
> Insbesondere braucht lto1 nicht mehr die Syntax der Eingabe zu checken;
> das ist alles schon passiert.

Ah! Jetzt hab ich es komplett verstanden. Wirklich pfiffig.

Danke.

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.