Servus zusammen,
ich stehe vor einem klein Problem. Und zwar hab ich für ein Uniprojekt
einen großen kleinen Fehler gemacht, indem ich statt den Großen die
kleinen AVRs ausgewählt habe... Nunja jetzt sitze ich hier vor meinem
ATtiny24 mit nur 2kb Speicher und bekomme das Programm um ein paar Byte
nicht in den Speicher.
Nun habe ich herausgefunden, dass offenbar vor allem das lesen der
hi-Bytes einiger uint16_t-Variablen recht verschwenderisch ist.
( Momentan mache ich das mit: hi = (uint8_t)(word>>8); )
Jetzt habe ich natürlich schon das große G bemüht, doch leider erschlägt
mich dieses mit tausenden Forenbeiträgen, die sich zwar ums gleiche
Problem drehen, aber leider keine eindeutigen Lösungen bieten.
Da jetzt in meinem speziellen Fall auch noch erschwerend hinzu kommt,
dass ich im Bereich der Hardwarenahen und vor allem Hardware sparsamen
Programmierung sehr neu bin, fehlt mir hier ein wenig Know-how.
Nach allem was ich gelesen habe scheint ja eine union wie folgende recht
geeignet zu sein.
1
typedefunion
2
{
3
uint16_ti;
4
uint8_tb[2];
5
}u16_t;
allerdings frage ich mich wie ich dabei sicher herausfinde welches das
high- und welches das low-byte ist...
Oder gibt es alternative Ansätze die effizienter sind? inline assembler
vielleicht? ich kann damit kaum umgehen aber eine Lösung muss her!
Ich bitte euch zudem mir nicht zum kauf neuer ICs zu raten. Dass dies
die einfachste Lösung wäre ist mir klar, allerdings werde ich dann dann
von meinem Betreuer gekillt, auch wenn sie nur sehr wenig kosten...
Ich danke euch schonmal für eure Unterstützung
Gruß
Leo
> ATtiny24 mit nur 2kb Speicher und bekomme das Programm um ein paar Byte> nicht in den Speicher.
Ich kenn die AVRs nicht, aber vielleicht gibts da ja was ähnliches. Beim
PIC gibt es ein Linker-File, in dem die Speicherbereiche aufgeteilt
sind. Dort ist es manchmal möglich, noch einige ungenutzte Bytes
herauszuquetschen.
> Momentan mache ich das mit: hi = (uint8_t)(word>>8);
So hätt ich das auch gemacht.
> Oder gibt es alternative Ansätze die effizienter sind? inline assembler> vielleicht? ich kann damit kaum umgehen aber eine Lösung muss her!
Schau mal was der Compiler draus macht. Evt. ist es gar nicht möglich,
dieses Problem effizienter zu lösen. Dann brauchst du dich auch nicht
weiter darum zu kümmern.
Hast du beim Compiler die Optimierung eingeschalten?
Evt. ist es möglich, die Speicheradresse der Variablen manuell zu
definieren. Dann könntest du mit Assembler das höhere Byte direkt
auslesen.
Der Compiler ist schon so schlau, dass er den Shift um 8 Stellen als
Zugriff aus der High-Byte erkennt. Wie Du es jetzt machst, ist schon
genau richtig. Du musst aber natürlich die Compiler-Optimierung
einschalten (Optimize for Size, -Os). Wenn Du Zweifel daran hast, schau
Dir den Assembler-Code, den der Compiler ezeugt, an.
Die Methode mit der Union ist Murks, eben weil je nach Prozessorfamilie
die Bytereihenfolge (Endianness) unterschiedlich sein kann.
Kannst ja mal einen beliebigen Ausschnitt aus dem Code posten und
eventuell sieht man gleich eine Optimierungsmöglichkeit... wenn es nur
um ein paar Bytes geht...
Leo B. schrieb:> Nun habe ich herausgefunden, dass offenbar vor allem das lesen der> hi-Bytes einiger uint16_t-Variablen recht verschwenderisch ist.> ( Momentan mache ich das mit: hi = (uint8_t)(word>>8); )
Ein Testfall wäre hilfreich.
avr-gcc 4.7 compiliert mit -Os folgenden Testfall
1
#include<stdint.h>
2
3
uint8_tget_hi(uint16_tword)
4
{
5
uint8_thi=(uint8_t)(word>>8);
6
returnhi;
7
}
zu:
1
get_hi:
2
mov r24,r25
3
ret
Da ist nix mehr zu optimieren...
> typedef union> {> uint16_t i;> uint8_t b[2];> } u16_t;
Dazu sagt C99 6.2.6.1 #7:
>> When a value is stored in a member of an object of union type,>> the bytes of the object representation that do not correspond>> to that member but do correspond to other members take unspecified>> values,
Konkret: Wenn dein Programm in C stehen soll, kannst du keine Union
verwenden, um die einzelnen Bytes zu bekommen.
>> note that this is a GCC extension which might not work with>> other compilers
Bedeutet: In GCC wird obiges Konstrukt — bzw. was du damit anstellen
willst — unterstützt, siehe
http://gcc.gnu.org/bugs/#nonbugs_c> Oder gibt es alternative Ansätze die effizienter sind? inline> assembler vielleicht? ich kann damit kaum umgehen aber eine> Lösung muss her!
Auch Inline-Assembler ist kein C.
Vermutlich gibt es viele andere Stellen im Code, wo gespart werden kann.
Leo B. schrieb:> Ich bitte euch zudem mir nicht zum kauf neuer ICs zu raten. Dass dies> die einfachste Lösung wäre ist mir klar, allerdings werde ich dann dann> von meinem Betreuer gekillt, auch wenn sie nur sehr wenig kosten...
ich kriege z.B. die tiny85 billiger als die tiny25,
ist vielleicht bei tiny24 auch so ...
Leo B. schrieb:> Nun habe ich herausgefunden, dass offenbar vor allem das lesen der> hi-Bytes einiger uint16_t-Variablen recht verschwenderisch ist.
Das halte ich für ein Gerücht.
Laß Dir das List-File erzeugen und schau nach, wo viel Code entsteht.
Während der Entwicklung könnte man aber auch einfach erstmal nen
ATtiny84 einstecken.
Peter
Danke erst einmal, für eure Hilfe!
Den ganzen Code werde ich jetzt mal nicht posten. Zum Einen ist der
nicht sehr sauber geschrieben in Bezug auf Kommentierung und
Formatierung, zum anderen müsst ihr nicht meine Arbeit machen. Aber
danke trotzdem.
Ich nutze jetzt den avr-gcc 4.7.2 (vorher 4.3.3) und compiliere mit -Os:
mein test.c - File:
1
#include<stdint.h>
2
#include<avr/io.h>
3
4
typedefstruct
5
{
6
uint8_tlo;
7
uint8_thi;
8
}hilobyte_t;
9
10
typedefunion
11
{
12
uint16_ti;
13
hilobyte_tb;
14
}u16_t;
15
16
17
volatileu16_tuni;
18
volatileuint8_tu8;
19
volatileuint16_tu16;
20
21
intmain(void)
22
{
23
24
uni.i=ADC;
25
u16=ADC;
26
u8=PINA;
27
28
while(1)
29
{
30
31
if(uni.b.hi<128)
32
continue;
33
if((volatileuint8_t)(u16>>8)<128)
34
continue;
35
if(*((volatileuint8_t*)(&u16)+1)<128)
36
continue;
37
if(u8<128)
38
continue;
39
40
asm("NOP");// damit das letzte if-Statement nicht weg optimiert wird
41
}
42
43
return0;
44
}
nach dem Compilieren kommt das Angehängte .lss-file raus.
So wie ich das deute ist alles gut nur die shift-operation nicht... Da
will er ums verrecken den ganzen volatile uint16 laden. vermute es liegt
am volatile, den ich aber zwingend brauche. Und gerade die Methode mit
dem shiften ist die einzige, welche mir sicher das high-byte liefert.
Das ist schon doof.
Leo B. schrieb:> Danke erst einmal, für eure Hilfe!> Den ganzen Code werde ich jetzt mal nicht posten.
Das wirst du aber müssen.
Vergiss die Idee mit dem Hibyte-LowByte. Das ist nicht dein Problem.
Dein Problem sind Codefresser ganz anderer Natur. Die können wir dir
aber nicht suchen helfen, wenn wir nicht deinen richtigen Code sehen.
Wenn das Ausheben einer Baugrube zu lange dauert, dann bringt es nichts
an der Optimierung des Sandkastenschäufelchens zu feilen. Klotzen, nicht
kleckern!
> Zum Einen ist der> nicht sehr sauber geschrieben in Bezug auf Kommentierung und> Formatierung,
Die Kommentierung ist mir ehrlich gesagt wurscht. Die ignoriere ich
sowieso, weil sie in den meisten Fällen nichtssagend ist und nicht
weiter hilft. Und in Punkto Formatierung solltest du das schleunigst IN
DEINEM EIGENEN INTERESSE bereinigen. Ein sauber geschriebenes Programm
ist nicht einfach nur Selbstzweck. Eine ordentliche Formatierung hilft
auch bei der Fehlersuche und Fehlervermeidung.
Leo B. schrieb:> asm("NOP"); // damit das letzte if-Statement nicht weg optimiert wird
warum sollte soetwas passieren?
das Problem könnte aber mit dem volatile zusammenhängen. Du solltest die
Variabel einmal laden und dann ohne volatile verwenden.
In diesem Beispiel macht das volatile eh keinen sinn, hier kannst du es
weglassen.
> vermute es liegt am volatile,
logisch.
Genau das ist ja der Sinn von volatile: Keine Optimiertricks erlaubt.
Jeder Zugriff hat so zu erfolgen, wie er im Code steht. Und in deinem
Code steht nun mal, dass eine 16-Bit Variable gelesen werden soll. Also
wird auch genau das gemacht.
> den ich aber zwingend brauche
Auch da gibts unter Umständen Möglichkeiten.
Zeig deinen richtigen Code, sonst hampeln wir am Montag immer noch rum.
Leo B. schrieb:> Den ganzen Code werde ich jetzt mal nicht posten.
Das ist schlecht.
Ich nehme auch an, dass es am volatile liegt. Die Methode, das in den
Griff zu bekommen, ist, das volatile in eine lokale Variable
umzuspeichern, mit dieser alle Operationen, die notwendig sind,
durchzuführen und das Ergebnis (falls erforderlich!) wieder
zurückzuspeichern.
Aber ohne den realen Code wird das nichts - nur Stochern im Nebel.
asm("NOP");// damit das letzte if-Statement nicht weg optimiert wird
8
}
Was soll der Quatsch? Klar kann der Compiler das wegoptimieren:
1. Wenn u8 < 128 ---> continue
2. Anderenfalls am Ende der while-Schleife ----> AUCH continue!
Dieses if-Statement ist total überflüssig - nicht nur für den Compiler,
sondern auch für den gesunden Menschenverstand.
Das gleiche gilt für alle anderen if-Statements in der while-Schleife.
Die können ALLE WEG, DENN SIE TUN NICHTS! Du versuchst das mit volatile
auszugleichen: kompletter Unsinn.
Wenn der restliche Code ähnlich aussieht, nehme ich mal an, dass man den
vom Compiler erzeugten Code auf 50% verkleinern kann - wenn man es
richtig macht.
> allerdings frage ich mich wie ich dabei sicher herausfinde> welches das high- und welches das low-byte ist...
In dem du ein Testprogramm schreibst.
Es hängt nämlich davon ab, auf welcher Art von Prozessor das C-Programm
läuft.
Auf Prozessoren von INtel mit low Endian wo das niederwertigste Byte
zuerst kommt, oder auf Prozessoren bei denen das höher wertige Byte
zuerst kommt.
Es ist also Prozessorabhängig, ändert sich aber auf der Plattform nicht
von byte zu byte.
Dann hast du schon einen effizienten Zuriff auf Bytes eines Worts. Ob
das allerdings schneller ist als ein (vom Compiler optimierter) Zugriff
>>8, ist dann ebenfalls plattformabhängig (und hängt damit vom Compiler
ab).
Vielleicht verbrät sein Programm ja Platz woanders. Beispielsweise
unnötig funktionslokale Variablen, mach sie global, das Spart Platz, auf
uC muss man mnachmal anders denken.
MaWin schrieb:> Beispielsweise> unnötig funktionslokale Variablen, mach sie global, das Spart Platz, auf> uC muss man mnachmal anders denken.
genau das Gegenteil. lokale Variabel können im Register bleiben, globale
müssen immer aus und in den Ram geladen werden.
MaWin schrieb:> Beispielsweise> unnötig funktionslokale Variablen, mach sie global, das Spart Platz,
Hm. ???
Ich würde eher den oben gegebenen Tipps folgen, und per .lss und
.map-File anaylsieren, welche Programmteile wieviel Platz benötigen, und
dann fallweise optimieren. Geniale
Super-Duper-Rundum-Sorglos-Optimierungstricks sind häufig urban legends
aus der Compiler-Steinzeit.
Oliver
Leo B. schrieb:> Da> will er ums verrecken den ganzen volatile uint16 laden.
Er macht nicht, was er will, sondern was Du ihm mit dem volatile
befiehlst.
Das Listing ist vollkommen korrekt.
Nur was Du als C-Code hingeschrieben hast, ist Mumpitz.
Wenn der Rest auch so aussieht, na dann gute Nacht.
Volatile nach volatile zu casten, ist auch Unsinn.
Peter
Oliver S. schrieb:> Geniale> Super-Duper-Rundum-Sorglos-Optimierungstricks sind häufig urban legends> aus der Compiler-Steinzeit.
Das kann man nicht laut genug sagen!
Programmoptimierung beginnt nicht damit, dass man einzelne Anweisung
durch andere Anweisungen ersetzt und dann plötzlich alles wundersamer
Weise um 50% schneller, besser, kleiner ist. Programmoptimierung beginnt
mit dem großen Bild des Codes ... auf algorithmischer Ebene.
Den Kleinkram überlässt man dem Compiler. Selbst dann, wenn der manchmal
nicht ganz so optimiert, wie man sich das vorstellt. Im großen und
ganzen sind das dann Peanuts, die in 99% aller Fälle nicht weiter ins
Gewicht fallen. Und nein, mit seinem eigenen Programm ist man nicht bei
diesem 1% dabei. Wer in dieser Kategorie operiert, der weiß das und muss
solche Fragen nicht mehr in einem Forum stellen.
Anders ausgedrückt: Egal wie gut ich einen Bubble-Sort optimiere, die
Performance eines Quick-Sort werde ich damit nie erreichen. Selbst dann
nicht, wenn der Quick-Sort schlecht geschrieben ist.
Und mit Codegrößen ist es ähnlich.
> genau das Gegenteil.
Nein. Probier's aus.
> lokale Variabel können im Register bleiben,> globale müssen immer aus und in den Ram geladen werden.
Nur wenn volatile dransteht.
MaWin schrieb:>> genau das Gegenteil.> Nein. Probier's aus.
mach ich ständig ich verwende sie immer wenn sie nicht global sein
müssen
>> lokale Variabel können im Register bleiben,>> globale müssen immer aus und in den Ram geladen werden.>> Nur wenn volatile dransteht.
jain, wenn sie volatile dran steht dann ist es immer der Fall, wenn es
nicht dran steht dann macht er es aber auch sehr oft. Spätestens in
einer ISR werden sie IMMER gelaaden und gespeichert.
Dann brauche globale Variable auch noch platz im flash. Also nur
nachteile gegenüber lokalen Variablen.
Johann L. schrieb:>>> the bytes of the object representation that do not correspond>>> to that member but do correspond to other members take unspecified>>> values,>> Konkret: Wenn dein Programm in C stehen soll, kannst du keine Union> verwenden, um die einzelnen Bytes zu bekommen.
Das hat hier kürzlich schon mal jemand nicht richtig verstanden.
Dieser Abschnitt beschreibt das Verhalten von Unions bei Verwendung
unterschiedlich großer Elemente, also z.B. das Mischen von char und int.
Das aber ist hier nicht der Fall, hier sind beide Elemente gleich groß.
Rufus Τ. Firefly schrieb:> Johann L. schrieb:>>>> the bytes of the object representation that do not correspond>>>> to that member but do correspond to other members take unspecified>>>> values,>>>> Konkret: Wenn dein Programm in C stehen soll, kannst du keine Union>> verwenden, um die einzelnen Bytes zu bekommen.>> Das hat hier kürzlich schon mal jemand nicht richtig verstanden.>> Dieser Abschnitt beschreibt das Verhalten von Unions bei Verwendung> unterschiedlich großer Elemente, also z.B. das Mischen von char und int.>> Das aber ist hier nicht der Fall, hier sind beide Elemente gleich groß.
Gut, denn steht auf der GCC-Webseite http://gcc.gnu.org/bugs/#nonbugs_c
offenbar totaler Schrott.
Johann L. schrieb:>> typedef union>> {>> uint16_t i;>> uint8_t b[2];>> } u16_t;>> Dazu sagt C99 6.2.6.1 #7:>>>> When a value is stored in a member of an object of union type,>>> the bytes of the object representation that do not correspond>>> to that member but do correspond to other members take unspecified>>> values,>> Konkret: Wenn dein Programm in C stehen soll, kannst du keine Union> verwenden, um die einzelnen Bytes zu bekommen.
Wenn man das kombiniert mit 6.5, #7:
7 An object shall have its stored value accessed only by an lvalue
expression that has one of the following types: {footnote 73}
...
a character type.
Wenn ich das richtig verstehe, ist ein (der einzige?) Weg, der gehen
muß:
1
uint16_ti;
2
char*b;
3
charbyte1,byte2;
4
5
b=&i;
6
7
byte1=b[0];
8
byte2=b[1];
wobei natürlich die endianness bestimmt, wo high und lowbyte hinkommen.
Johann L. schrieb:> Gut, denn steht auf der GCC-Webseite http://gcc.gnu.org/bugs/#nonbugs_c> offenbar totaler Schrott.
Magst Du versuchen, mir zu zeigen, was exakt Du meinst? Das da
aufgeführte Beispiel der Verwendung einer union beschreibt, wie ich auch
erwähnte, den Effekt der Verwendung zweier gleichgroßer Elemente der
Union, die sich erwartungsgemäß verhalten. Und nicht andersherum.
Entweder verstehe ich immer noch nicht, was Du versuchst zu sagen, oder
unser englisches Textverständnis unterscheidet sich deutlich.
Rufus Τ. Firefly schrieb:> Johann L. schrieb:>> Gut, denn steht auf der GCC-Webseite http://gcc.gnu.org/bugs/#nonbugs_c>> offenbar totaler Schrott.>> Magst Du versuchen, mir zu zeigen, was exakt Du meinst? Das da> aufgeführte Beispiel der Verwendung einer union beschreibt, wie ich auch> erwähnte, den Effekt der Verwendung zweier gleichgroßer Elemente der> Union, die sich erwartungsgemäß verhalten. Und nicht andersherum.
So wie ich C99 6.2.6.1 #7 verstehet, werden durch Schreiben einer
Komponente X einer Union alle anderen Komponenten ungültig -- unabhängig
von den Aliasing-Regeln, derer sich andere Klauseln annehmen:
1) X wird geschrieben
2) Y wird gelesen.
Der Compiler geht nun hin und analysiert den Daten- und Codefluss. Kann
der Compiler beim Lesen von Y nachweisen, daß das letzte geschriebene
Element nicht Y war (was hier der Fall sein soll), dann darf angenommen
werden, daß Y keinen spezifizierten Inhalt hat. Und zwar unabhängig von
seinem Typ und von seiner Größe und den Aliasing-Regeln.
> Auf der GCC-Seite steht:>> To fix the code above, you can use a union instead of a cast> (note that this is a GCC extension which might not work with> other compilers):> #include <stdio.h>>> int main()> {> union> {> short a[2];> int i;> } u;>> u.a[0]=0x1111;> u.a[1]=0x1111;>> u.i = 0x22222222;>> printf("%x %x\n", u.a[0], u.a[1]);> return 0;> }>> Now the result will always be "2222 2222".
Dies ist also eine GCC-Erweiterung; man beachte das "might not work with
other compilers". Und im darauffolgenden Link steht:
> There are cases where you wish to access the same memory as> different types:>> float *f = 2.718;> printf("The memory word has value 0x%08x\n", *((int*)f));>> You cannot do that in ISO C, but gcc has an extension in that> it considers memory in unions as having multiple types, so the> following will work in gcc (but is not guaranteed to work in> other compilers!)>> union {> int i;> float f;> } u;> u.f = 2.718;> printf("The memory word has value 0x%08x\n", u.i);>> One bieffect of this is that gcc may miss optimization> opportunities when you use union-heavy constructs.
Dies wird also in GCC unterstützt, aber nicht unbedingt in anderen
Compilern.
In GCC funktioniert das übrigens unabhängig von -f[no-]strict-aliasing,
wodurch Optimierungen aufgrund von Aliasing ab- bzw. anschaltet werden.
Der einzig standardkonforme Weg, die Bits von einem float zu bekommen,
ist damit memcpy (mir fällt jedenfalls kein anderer Weg ein ausser
Inline Assembler, und der ist kein Standard):
Johann L. schrieb:> Der einzig standardkonforme Weg, die Bits von einem float zu bekommen,> ist damit memcpy
Das ist auch der einzige in C99 explizit erwähnte Weg, und es ist
übrigens auch ein sehr effizienter, zumindest bei GCC, da memcpy dort
bereits vom Compiler selbst implementiert und sehr gut optimiert ist.
Johann L. schrieb:> So wie ich C99 6.2.6.1 #7 verstehe
Das würde implizieren, daß Unions ein ziemlich nutzloses Sprachfeature
sind. Ob das die Absicht ist?
Rufus Τ. Firefly schrieb:> Johann L. schrieb:>> So wie ich C99 6.2.6.1 #7 verstehe>> Das würde implizieren, daß Unions ein ziemlich nutzloses Sprachfeature> sind. Ob das die Absicht ist?
Nein, es impliziert, daß unions eigentlich für was ganz anderes gedacht
sind.
Wir drehen uns hier im Kreis.
> When a value is stored in a member of an object of union type,> the bytes of the object representation that do not correspond> to that member but do correspond to other members take unspecified> values,
Offensichtlich haben wir sehr unterschiedliche Auffassungen, wie das zu
übersetzen ist.
> Wenn ein Wert in einem Element einer Union gespeichert wird, erhalten> die Bytes der Union, die nicht zu diesem Element, aber zu anderen> Elementen gehören, undefinierte Werte.
(Hervorhebung von mir)
Letztendlich findet sich in Anhang J.1 "Unspecified behavior" noch:
"The following are unspecified:
[...]
— The value of a union member other than the last one stored into
(6.2.6.1)."
In 6.5.2.3/5 wird noch eine einzige Ausnahme genannt:
"One special guarantee is made in order to simplify the use of unions:
if a union contains several structures that share a common initial
sequence (see below), and if the union object currently contains one of
these structures, it is permitted to inspect the common initial part of
any of them anywhere that a declaration of the complete type of the
union is visible."
Wenn ich also mehrere Strukturen in der union habe, die alle exakt
gleich anfangen, dann darf ich diesen Anfang auch über das falsche
Member nutzen.
Ach so, was ich vergessen habe:
Rufus Τ. Firefly schrieb:>> Wenn ein Wert in einem Element einer Union gespeichert wird, erhalten>> die Bytes der Union, die nicht zu diesem Element, aber zu anderen>> Elementen gehören, undefinierte Werte.>> (Hervorhebung von mir)
Das stimmt schon. Es bezieht sich nur auf die zusätzlichen Bytes, wenn
man ein Member speichert, das einen kleineren Typ hat. Es sagt erstmal
noch gar nichts darüber aus, was passiert, wenn man ein anderes als das
zuletzt geschriebene Element liest.
Rufus Τ. Firefly schrieb:>> When a value is stored in a member of an object of union type,>> the bytes of the object representation that do not correspond>> to that member but do correspond to other members take unspecified>> values
Das heißt aus meiner Sicht einfach nur, dass man in einer Union (im
Gegensatz zu einem Struct) nur eines der Elemente beschreiben darf, wenn
man das Element später mit dem gleichen Inhalt wieder lesen möchte.
Der Teil "that do not correspond to that member" ist in dem Satz nötig,
weil er sonst ja sagen würde, dass auch die Bytes unspezifizierte Werte
annehmen können, die zu dem gerade beschriebenen Element gehören. Nur
weil ein Byte zu einem anderen Element gehört, heißt ja nicht, dass es
nicht gleichzeitig auch zum gerade beschreibenen Element gehören kann.
Das ist ja genau der Witz einer Union.
Also im Klartext: Die Bytes, die man beschreibt, wenn man auf ein
Element zugreift, kann man später auch wieder richtig auslesen. Dass die
Daten von anderen Elementen erhalten bleiben, auf die man vorher mal
geschrieben hat, kann dabei aber nicht garantiert werden (da sich in der
Praxis eben alle Elemente den gleichen Speicher teilen).
Mit der Länge der Elemente hat das nichts zu tun. Wenn alle Elemente
gleich lang sind, ist es selbstverständlich, denn der Speicher ist ja
nur einmal da. Bei unterschiedlich langen Elementen kann es aber halt
auch sein, dass Teile von früher beschriebenen Elementen überleben. Das
sichert einem der Standard aber nicht zu.
Sehr interessant!
Ich hab bisher auch gedacht, daß der Sinn einer Union quasi ein
erweiterter Cast sei, d.h. Daten mit einem Datentyp reinschreiben und
mit anderem Datentyp wieder herauslesen.
Bronco schrieb:> Ich hab bisher auch gedacht, daß der Sinn einer Union quasi ein> erweiterter Cast sei, d.h. Daten mit einem Datentyp reinschreiben und> mit anderem Datentyp wieder herauslesen.
Da hast du falsch gedacht. Allerdings wird die union seit ihrer
Erfindung erfolgreich für solche "casts" genutzt, und in den
allermeisten Fällen funktioniert das auch, egal, was der Standard dazu
sagt.
Oliver
Bronco schrieb:> Sehr interessant!> Ich hab bisher auch gedacht, daß der Sinn einer Union quasi ein> erweiterter Cast sei, d.h. Daten mit einem Datentyp reinschreiben und> mit anderem Datentyp wieder herauslesen.
Nein. Der Sinn und Zweck einer Union ist es, verschieden strukturierte
Daten "übereinanderzulegen", um ein- und denselben Speicherplatz zu
nutzen.
Ein Beispiel:
Ich habe ein Rack mit 24 Einschüben. In diese 24 Einschübe kann ich
verschiedene Steuerkarten reinstecken. Jede dieser Steuerkarten (z.B.
Echtzeituhr, Modul mit 16 Ausgängen, Modul mit 16 Eingängen) braucht
verschiedene Betriebsparameter, z.B:
Echtzeituhr: Adresse, Stunde, Minute, Sekunde
16 Ausgänge: Adresse, 16 Bit Ausgänge
16 Eingänge: Adresse, 16 Bit Eingänge, Flags, wie z.B. Flankentriggerung
Dann könnte man definieren:
union
{
struct rtc_st rtc;
struct out_st outputs;
struct in_st inputs;
} slots[24];
Wenn ich jetzt eine Echtzeituhr in Slot 0 stecke, schreibe ich:
slots[0].rtc.address = 4711;
slots[0].rtc.hour = 14;
slots[0].rtc.minute = 22;
slots[0].rtc.second = 13;
Ich käme aber nie auf die Idee, die gesetzte Uhrzeit über ein anderes
mögliches Elektronik-Modul (wie z.B. über slots[0].inputs) wieder
auszulesen.
Wirklich schöne verständliche Erklärung, auch für nicht-profis wie mich.
Allerdings muss ich sagen, dass ich in den letzten Tagen gelernt habe,
dass diese nicht-Standard-konforme Verwendung von unions sehr gut
funktioniert und dem Compiler offenbar auch besser schmeckt als alles
was ich sonst so probiert habe. Kleines Beispiel, zwei Wege ein globales
volatile uint16_t ins eeprom zu schrieben:
wie man sieht, konnte der Compiler die Daten über den union-Missbrauch
(u16_t definition im Anhang) mit wesentlich weniger Code ins eeprom
verfrachten als z.B. mit der "(hi<<8)&lo"-Methode.
Ums wieder anfassbarer zu beschreiben. Wenn ich ein Loch entgraten
möchte, muss ich da zwingend einen Entgrater verwenden, oder kann ich
nicht auch einen großen Bohrer verwenden, wenn ich mir der Gefahr
bewusst bin, mit dem Bohrer das Loch leicht zerstören zu können?
Anhang:
Also gerade mal 2 Words mehr als mit Deiner Union.
Wieviel 1000-mal copy&pastest Du denn diese Codesequenz, damit das einen
merkbaren Mehrverbrauch an Flash ergibt?
Peter
Leo B. schrieb:> wie man sieht, konnte der Compiler die Daten über den union-Missbrauch> (u16_t definition im Anhang) mit wesentlich weniger Code ins eeprom> verfrachten als z.B. mit der "(hi<<8)&lo"-Methode.
War da evtl. die Optimierung ausgeschaltet? Der Code kopiert ja munter
völlig unnötig irgendwelche Register hin und her und macht allgemein
jede Menge sinnloser Operationen.
Rolf Magnus schrieb:> Leo B. schrieb:>> wie man sieht, konnte der Compiler die Daten über den union-Missbrauch>> (u16_t definition im Anhang) mit wesentlich weniger Code ins eeprom>> verfrachten als z.B. mit der "(hi<<8)&lo"-Methode.>> War da evtl. die Optimierung ausgeschaltet? Der Code kopiert ja munter> völlig unnötig irgendwelche Register hin und her und macht allgemein> jede Menge sinnloser Operationen.
Der Meinung bin ich auch.
Ich nutze den AVR-GCC-4.7.2 mit -Os optimierung... liegt wohl wieder am
volatile dass GCC da so nen mist baut
Leo B. schrieb:> liegt wohl wieder am> volatile dass GCC da so nen mist baut
Nö, Du bist es, der ihm volatile befiehlt.
Mein Eindruck ist, daß Du volatile sehr freigiebig mit der Schöpfkelle
verteilt hast.
Hör auf, nach einzelnen Words zu fischen, das bringt nichts.
Suche die Stellen, wo wirklich viel Code entsteht.
Peter
> Mein Eindruck ist, daß Du volatile sehr freigiebig mit der Schöpfkelle> verteilt hast.
Auch wenn's nicht ganz Toppic ist:
volatile ist z.B. dort nötig, wo eine Interrupt-Routine Daten
reinschreiben darf und die main-schleife sie raus lesen soll. liege ich
da etwa falsch?
> Hör auf, nach einzelnen Words zu fischen, das bringt nichts.> Suche die Stellen, wo wirklich viel Code entsteht.
Hab ich doch schon lange, drum bin ich ja bereits auf 884byte runter,
aber in diesem Thread ist eben nicht das Thema "Codeoptimierung" sondern
eben das effiziente Ansprechen der einzelnen Bytes eines uint16_t.
Ausserdem finde ich es gerade hoch interessant rauszufinden, wie man mit
dem GCC-Compiler am effizientesten an die genannten Bytes ran kommt.
Leo B. schrieb:> Ich nutze den AVR-GCC-4.7.2 mit -Os optimierung... liegt wohl wieder am> volatile dass GCC da so nen mist baut
Bist du sicher, daß die beiden Varianten, die du vergleichst, nicht
Äpfel und Birnen sind?
Sind beide Varianten gleichbedeutend, insbesondere was deren
Seiteneffekte (volatile) angeht?
Leo B. schrieb:> Ausserdem finde ich es gerade hoch interessant rauszufinden, wie man mit> dem GCC-Compiler am effizientesten an die genannten Bytes ran kommt.
Am effizientesten geht das, indem du es dem Compiler beibringst. Die
Quelle zu hacken ist keine Lösungen; es ist immer noch Hack...
Einen ersten Hinweis, wo der Code nicht so ist, wie du erwartest,
findest du mit den Dumps aus
-fdump-rtl-all -fdump-tree-all -dP -save-temps
Und nicht Äpfel mit Birnen vergleichen, gell?
Johann L. schrieb:> Bist du sicher, daß die beiden Varianten, die du vergleichst, nicht> Äpfel und Birnen sind?
ein uint16 aus einem volatile uint16_t array soll ins eeprom...
beide Methoden tun das, wo unterscheidet sich "dieser Apfel" von "dieser
Birne"?
Johann L. schrieb:> Einen ersten Hinweis, wo der Code nicht so ist, wie du erwartest,> findest du mit den Dumps aus>> -fdump-rtl-all -fdump-tree-all -dP -save-temps
Würde ich jetzt gerne verstehen, meine Kenntnisse sind aber offenbar zu
gering. Was soll das bedeuten und was genau finde ich damit heraus?
Johann L. schrieb:> Am effizientesten geht das, indem du es dem Compiler beibringst.
Darauf glaube ich habe ich keinen Einfluss solange ich mir keinen
eigenen Compiler programmieren möchte, was ich definitiv nicht kann!
Leo B. schrieb:> eeprom_write_word( &ee_temp_high, (spi_rx_buf[1]<<8) + spi_rx_buf[2] );
probier doch Mal statt dessen
uint16_t temp;
temp = spi_rx_buf[1]<<8 + spi_rx_buf[2];
eeprom_write_word( &ee_temp_high, temp );
das sollte ähnlich kurz wie deine 1.Version sein
Leo B. schrieb:> volatile ist z.B. dort nötig, wo eine Interrupt-Routine Daten> reinschreiben darf und die main-schleife sie raus lesen soll.
2 .. 3 Variablen als volatile sollte aber keinen merkbaren Einfluß auf
die Gesamtgröße des Programms haben.
Du doktorst hier immer nur an kleinsten Codestückchen rum, die kaum
Flash brauchen. Zeig dochmal die großen Brocken, da lohnt sich dann
optimieren.
Peter
This is free software; see the source for copying conditions. There is NO
5
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Wird aus dem AVR-Studio4 mit den Parametern im Anhang ausgeführt und
compiliert aus dem anghängten Test.c das angehängte Test.lss
Warum? ich weiß es nicht. Compiliert bei irgend jemandem der avr-gcc
etwa "besser"?
-fno-split-wide-types wenn dich die paar Instruktionen stören. Ist
vermutlich PR52278, evtl. verbandelt mit PR41076.
http://gcc.gnu.org/PR52278http://gcc.gnu.org/PR41076
Keiner der von dir genannten Schalter ausser -Os ist ein
Optimierungsschalter.
Dein Code passt doch prima in den Tiny24, ist doch noch massig Platz
übrig, vor allem, wenn du das zusammenbasteln deines ints nur einmal
machst.
MfG Klaus
Läubi .. schrieb:> uint16_t word = ( hi << 8 ) + lo;
Mal so eine Frage: warum wird hier (und auch weiter oben) eine Addition
benutzt. Für mich ist das ein Zusammensetzen von Bitfeldern und dort
wäre ein Oder gefragt? Bei Feldern kleiner als ein Byte macht man das
doch auch nicht mit arithmetischen Operationen.
uint16_t word = ( hi << 8 ) | lo;
MfG Klaus
Klaus schrieb:> Mal so eine Frage: warum wird hier (und auch weiter oben)> eine Addition benutzt.
Vom Ergbenis ist es (hier) gleichwertig, auch wenn ich hier eine |
normalerweise bevorzuge, ich habe aber den "orginal" Code einfach
kopiert und nicht darauf geachtet denk dir einfach ein | anstelle des +
:-)
Eventuell wird deshalb auch soviel unützer Code rangezogen, kann der TE
ja mal für sich ausprobieren.
Klaus schrieb:> Mal so eine Frage: warum wird hier (und auch weiter oben) eine Addition> benutzt.
Erstmal ist die Addition hier nicht grundsätzlich falsch. Trotzdem hast
Du schon recht: eigentlich ist es Unsinn.
Der Grund, warum es doch einige Leute so machen:
Es gab in der Vergangenheit gcc-Versionen, welche bei der Plus-Variante
kürzeren Code als bei der Oder-Variante erzeugt haben. So steckt das '+'
hier manchen halt "im Blut". Bei aktuellen gcc-Versionen spielt das
keine Rolle mehr. Der erzeugte Code ist dann exakt gleich lang, sieht
aber nicht gleich aus.
Es ist schon erstaunlich, welche Unmenge an Zeit investiert wird, um nur
wenige Words einzusparen. Mir wäre meine Zeit dafür zu schade.
Effizienter ist es, auf der Programmablaufseite zu optimieren. Dann wird
meistens der Code auch einfacher und leserlicher.
Auch braucht man keine speziellen Compilertricks.
Peter
Naja, ich finde es schon bemerkenswert, dass der Compiler so etwas
simples nicht optimal hinbekommt. Ist ja nicht so, dass das
Zusammensetzen von 16 Bit aus zwei Mal 8 Bit und Übergabe an eine
Funktion so selten vorkäme. Da habe ich den GCC wirklich überschätzt.
Habe gestern auch ein bisschen mit dem Beispiel rumgespielt. Diese
Variante war auch dabei, aber da werden die Daten ebenfalls unnötig in
Registern rumkopiert:
1
uint8_thi=spi_rx_buf[1];
2
uint8_tlo=spi_rx_buf[2];
3
uint16_tword=(hi<<8)|lo;
4
eeprom_write_word(&ee_temp_high,word);
Was solls, sehen wir es positiv: In zukünftigen Versionen steckt noch
Potenzial für kleinere Codegrößen ...
Fabian O. schrieb:> In zukünftigen Versionen steckt noch> Potenzial für kleinere Codegrößen ...
Sehe ich nicht so.
Volatile ist ja nichts, was den Großteil eines Programms ausmacht, also
wird man daran (vermutlich) auch nichts optimieren.
Im Gegenteil, man wird darauf bedacht sein, daß bei volatile nicht
zuviel wegoptimiert wird.
Die Compilerbauer sind extrem daran interessiert, daß ihre Arbeit auch
einen großen Effekt hat, ehe sie etwas ändern.
Wenn ich mal überlege wieviel Jahre es gedauert hat, den 64Bit-Typen
etwas Sparsamkeit zu gönnen und da ging es um satte ~5kB!
Statt bei volatile um einzelne Words zu kämpfen, finde eine double Lib
erheblich sinnvoller. Mit float stößt man schon sehr oft an
Genauigkeitsgrenzen.
Peter
Fabian O. schrieb:> Ist ja nicht so, dass das> Zusammensetzen von 16 Bit aus zwei Mal 8 Bit und Übergabe an eine> Funktion so selten vorkäme.
Da sei die Frage erlaubt, was du unter "nicht selten" verstehst.
Peter hats ja schon geschrieben: Wer sich über die dabei
"verschwendeten" Bytes Gedanken macht, optimiert auf einer Ebene, die
bei 99% der Anwendungen und der Anwender schlicht uninteressant ist.
Beim restlichen Prozent ist entweder gcc der falsche Compiler, oder der
Programmierer für das Projekt nicht geeignet.
Oliver
Peter Dannegger schrieb:> Volatile ist ja nichts, was den Großteil eines Programms ausmacht, also> wird man daran (vermutlich) auch nichts optimieren.> Im Gegenteil, man wird darauf bedacht sein, daß bei volatile nicht> zuviel wegoptimiert wird.
Der Fehler hat ja nichts mit volatile zu tun. In dem Beispiel waren die
beiden Einzelbytes in getrennten nicht-volatile-Variablen. Der Übergabe
dieser beiden Variablen als ein 16-Bit-Wert an die Funktion ist das
Problem. Denn anstatt sie gleich in die richtigen Register zu legen
kommen sie zuerst in ein temporäres Register, werden mit Null verodert
und dann erst an die richtige Stelle kopiert.
Johann hat sogar die entsprechenden Bugs genannt:
http://gcc.gnu.org/PR52278http://gcc.gnu.org/PR41076Oliver S. schrieb:> Da sei die Frage erlaubt, was du unter "nicht selten" verstehst.
Praktisch überall, wo man 16 Bit von außen (einem Sensor, UART,
Netzwerk, Funk, ...) bekommt und weiterverarbeitet. Die Werte kommen
immer zuerst byteweise über ein Interface rein und müssen im Programm zu
einem uint16_t zusammengebaut werden. Wenn da jedes Mal solche
Sperenzchen gemacht werden, ist das in Summe imo schon bedeutsam für
Codegröße und Geschwindigkeit. Ich glaube/hoffe allerdings, dass dieser
Fehler nur unter bestimmten, seltenen Umständen auftritt, sonst wäre das
ja sicher schon mal in den Testfällen aufgefallen.
Fabian O. schrieb:> Die Werte kommen> immer zuerst byteweise über ein Interface rein und müssen im Programm zu> einem uint16_t zusammengebaut werden.
Das sind dann je Interface an genau 2 Stellen (Lesen, Schreiben), also
nicht der Rede wert.
Fabian O. schrieb:> Wenn da jedes Mal solche> Sperenzchen gemacht werden
Nur wenn man es als Spaghetticode (lange copy&paste Monster) an
hunderten Stellen macht.
Damit rechnen die Compilerbauer aber nicht, daß jemand so programmiert.
Derjenige muß dann eben nen größeren MC nehmen oder seinen
Programmierstil verbessern.
Peter
Es geht nicht nur um Codegröße, sondern auch um
Ausführungsgeschwindigkeit. Selbst wenn die unnötigen Befehle nur an
einer einzigen Stelle im Programm stehen, können sie mehrere tausend Mal
pro Sekunde ausgeführt werden (Beispiel: externer ADC).
Da hilft dann kein größerer MC oder "Programmierstil verbessern",
sondern das Gegenteil, nämlich den Hack über die Union zu gehen oder den
Teil in (Inline-)Assembler schreiben. Beides kein guter Programmierstil,
wenn es der Compiler genauso gut könnte.
Ich kann nicht nachvollziehen, wie man so einen offensichtlichen Bug
verteidigen kann. Klar geht die Welt davon nicht unter, aber es ist
nunmal ein Fehler des Compilers und nicht des Programmierers.
Na ja, für den einen ist das ein unverzeihlicher Bug, für andere ist das
zwar nicht perfekt, aber man kommt damit klar. Kein Compiler dieser Welt
erzeugt für alle denkbaren Sourcecodes den absolut
optimalen/schnellsten/kürzesten/ was auch immer Code. Wer das erwartet,
dem fehlt etwas Realitätssinn.
(avr-)gcc erzeugt unbetritten bei den 8-bittern nicht immer optimalen
Code.
Aber niemand muß einen bestimmten Compiler nutzen, und jeder darf beim
gcc Hand anlegen, und mithelfen, den zu verbessern. Der erste Schritt
wäre ein Bug-Report, auch wenn das zugegebermassen nichts bringen wird,
da sich die wenigen Resourcen dort lieber um die wirklich wichtigen
Probleme kümmern.
Oliver
Fabian O. schrieb:> Selbst wenn die unnötigen Befehle nur an> einer einzigen Stelle im Programm stehen, können sie mehrere tausend Mal> pro Sekunde ausgeführt werden
1000 mal/s sind 16000 Zyklen bei 16MHz, da willst Du wirklich um jeden
einzelnen Zyklus feilschen?
Mir wär das zu blöde.
Beim Ursprungspost ist das ja besonders kritisch. 6,8ms Schreiben in den
EEPROM, da sind zusätzliche 62,5ns bestimmt absolut tödlich :-)
Fabian O. schrieb:> nämlich den Hack über die Union zu gehen oder den> Teil in (Inline-)Assembler
Wenn Du damit glücklicher wirst.
Ich komme bestens ohne solche Krücken aus. Und manche Projekte laufen
sogar nur mit 1MHz.
Einen Overhead von 20% bei einem C-Programm gegenüber Assembler finde
ich völlig normal. Erst ab 100% würde ich mir (vielleicht) Gedanken
machen.
Peter
Ich hab mir auch mal Krücken für nen CAN-Bus machen müssen:
1
uint32_tswap_order(uint32_tval)// LSB first -> MSB first
2
{
3
union{
4
uint32_tu32;
5
uint8_tu8[4];
6
}in,out;
7
in.u32=val;
8
out.u8[0]=in.u8[3];
9
out.u8[1]=in.u8[2];
10
out.u8[2]=in.u8[1];
11
out.u8[3]=in.u8[0];
12
returnout.u32;
13
}
Das wurde von AVR-GCC recht effizient umgesetzt. Besser hätte ich es in
Assembler auch nicht machen können.
Und dann noch, um ein float als int32 zu übertragen ohne Cast:
1
staticinlineuint32_tmkp_u32(floatval)// make pointer from float to uint32_t
2
{
3
union{
4
uint32_tu32;
5
floatf;
6
}out;
7
out.f=val;
8
returnout.u32;
9
}
10
11
staticinlinefloatmkp_fl(uint32_tval)// make pointer from uint32_t to float
12
{
13
union{
14
uint32_tu32;
15
floatf;
16
}out;
17
out.u32=val;
18
returnout.f;
19
}
Diese Funktionen erzeugen 0 Byte Code, sie wandeln nur den Typ um.
Ganz ohne Union geht es manchmal doch nicht.
Peter
Fabian O. schrieb:> Ich kann nicht nachvollziehen, wie man so einen offensichtlichen Bug> verteidigen kann. Klar geht die Welt davon nicht unter, aber es ist> nunmal ein Fehler des Compilers und nicht des Programmierers.
Niemand bestreitet, daß der Code suboptimal ist, aber es ist definitiv
kein Fehler.
Fabian O. schrieb:> Der Fehler hat ja nichts mit volatile zu tun.
Korrekt. Wobei es aber auch Situationen gibt, wo für volatile besserer
Code erzeugt werden könnte, etwa PR49807.
> Johann hat sogar die entsprechenden Bugs genannt:> http://gcc.gnu.org/PR52278> http://gcc.gnu.org/PR41076
Das "PR" steht weder für "Bug" noch für "Fehler", sondern für "Problem
Report".
> Ich glaube/hoffe allerdings, dass dieser Fehler nur unter bestimmten,> seltenen Umständen auftritt, sonst wäre das ja sicher schon mal in> den Testfällen aufgefallen.
Gesetzt den Fall, es gäbe solch einen Testfall in der GCC-Testuite [2].
Wem bitte sollte das Problem aussallen? Es werden ja noch nichtmal
regelmässig AVR-Tests gefahren, siehe [1]. Nach "avr" suchst du da
vergebens, zumindest die letzten 10 Monate.
Ausserdem ist das Problem bekannt wie du an den genannten PRs siehst.
Es ist allerdings so, daß es niemanden gibt, der sich um
avr-gcc kümmert. Weil es niemanden interessiert oder niemand Bock drauf
hat oder niemand weiß oder wissen will wie es geht oder weil niemand es
als wichtig genug erachtet, professionellen Support dafür zu
organisieren oder zu bezahlen — noch nicht mal Atmel.
Und anderen GCC-Entwicklern von ARM oder von IBM oder von Google oder
von wo auch immer kann man schwerlich einen Vorwurf daraus machen, sich
nicht um eine ferner-liefern Architektur zu kümmern, von der sie noch
nie was gehört haben oder wissen wofür "AVR" steht.
Kurzum: Jeder, der sich auch nur ein bisschen mit AVRs auskennt, weiß,
wie guter Code aussieht. Jeder, der sich nur ein bisschen mit avr-gcc
beschäftigt hat — und sei es nur als Anwender — weiß, daß dieser
Compiler keinen optimalen Code erzeugt und daß se keinen Support gibt,
weil es niemanden auf dem Planeten gibt, dem es wichtig genug ist.
[1] http://gcc.gnu.org/ml/gcc-testresults
[2] http://gcc.gnu.org/viewcvs/trunk/gcc/testsuite/
Peter Dannegger schrieb:> Und dann noch, um ein float als int32 zu übertragen ohne Cast:
Und warum muß es unbedingt ohne Cast sein? Ich hab's grad mal mit dem
avr-gcc 4.7.0 ausprobiert, und da kommt bei mir für die union, den Cast
und das memcpy immer das selbe raus:
1
#include<stdint.h>
2
#include<string.h>
3
4
staticinlineuint32_tmkp_u32(floatval)// make pointer from float to uint32_t
5
{
6
union{
7
uint32_tu32;
8
floatf;
9
}out;
10
out.f=val;
11
returnout.u32;
12
}
13
14
voidfoo(uint32_tf);
15
16
voidtest1(floatval)
17
{
18
foo(mkp_u32(val));
19
}
20
21
voidtest2(floatval)
22
{
23
foo(*(uint32_t*)&val);
24
}
25
26
voidtest3(floatval)
27
{
28
uint32_ttmp;
29
memcpy(&tmp,&val,sizeof(val));
30
foo(tmp);
31
}
Und hier der generierte Assembler-Code:
1
.file "convert.c"
2
__SP_H__ = 0x3e
3
__SP_L__ = 0x3d
4
__SREG__ = 0x3f
5
__tmp_reg__ = 0
6
__zero_reg__ = 1
7
.text
8
.global test1
9
.type test1, @function
10
test1:
11
/* prologue: function */
12
/* frame size = 0 */
13
/* stack size = 0 */
14
.L__stack_usage = 0
15
rjmp foo
16
.size test1, .-test1
17
.global test2
18
.type test2, @function
19
test2:
20
/* prologue: function */
21
/* frame size = 0 */
22
/* stack size = 0 */
23
.L__stack_usage = 0
24
rjmp foo
25
.size test2, .-test2
26
.global test3
27
.type test3, @function
28
test3:
29
/* prologue: function */
30
/* frame size = 0 */
31
/* stack size = 0 */
32
.L__stack_usage = 0
33
rjmp foo
34
.size test3, .-test3
35
.ident "GCC: (GNU) 4.7.0"
Wie man sieht, wird in allen drei Fällen kein Code für die Konvertierung
erzeugt.
PS: Ich bin erstaunt, daß sogar das rcall/ret zu einem rjump optimiert
wird. Und da beschweren sich hier alle über die schlechte Optimierung.
Rolf Magnus schrieb:> Und warum muß es unbedingt ohne Cast sein? Ich hab's grad mal mit dem> avr-gcc 4.7.0 ausprobiert, und da kommt bei mir für die union, den Cast> und das memcpy immer das selbe raus:> void test2(float val)> {> foo(*(uint32_t*)&val);> }
Das ist kein Cast sondern Type-Punning ;-) Wenn mach durch einen
int-Zeiger in den Speicher greift (oder long, ist egal), kann der
Compiler davon ausgehen, daß ein float-Schrieb diese Sppeicherstelle
nicht verändert. Nennt sich Strict-Aliasing.
GCC sollte eine Warnung wie "type punning breaks strict aliasing rules"
o.ä. bringen.
Peter Dannegger schrieb:> @Rolf Magnus>> Ich wollte mir damit die extra Zwischenvariablen ersparen,
Ok. Um die ging es aber ja an sich gar nicht, sondern um die union, die
hier keinen Vorteil gegenüber anderen Methoden der Typ-Reinterpretation
hat. Die memcpy-Variante produziert auch nicht mehr Code, ist aber immer
noch weniger zu schreiben als die union, und ich finde sie auch besser
lesbar. Mir ist einfach nicht klar, warum so oft darauf bestanden wird,
trotzdem auf Teufel komm raus die eigentlich dafür gar nicht gedachte
union zu mißbrauchen.
Johann L. schrieb:> Das ist kein Cast sondern Type-Punning ;-)
Naja, ein Cast kommt auch drin vor ;-)
> Wenn mach durch einen int-Zeiger in den Speicher greift (oder long, ist> egal), kann der Compiler davon ausgehen, daß ein float-Schrieb diese> Sppeicherstelle nicht verändert. Nennt sich Strict-Aliasing.
Ich hab ehrlich gesagt nie verstanden, was es damit auf sich hat.
> GCC sollte eine Warnung wie "type punning breaks strict aliasing rules"
Ja, diese Warnung kommt.
1
convert.c:23:5: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
Rolf Magnus schrieb:> Ich hab ehrlich gesagt nie verstanden, was es damit [Strict-Aliasing]> auf sich hat.
Beispiel:
1
externlongx;
2
3
longf(float*y)
4
{
5
x=0;
6
*y=42;
7
returnx;
8
}
darf ein C-Compiler übersetzen wie
1
externlongx;
2
3
longf(float*y)
4
{
5
x=0;
6
*y=42;
7
return0;
8
}
und avr-gcc tut das auch:
1
f:
2
sts x,__zero_reg__ ; x,
3
sts x+1,__zero_reg__ ; x,
4
sts x+2,__zero_reg__ ; x,
5
sts x+3,__zero_reg__ ; x,
6
ldi r20,0 ; tmp44
7
ldi r21,0 ;
8
ldi r22,lo8(40) ; ,
9
ldi r23,lo8(66) ; ,
10
movw r30,r24 ; , y
11
st Z,r20 ; *y_1(D), tmp44
12
std Z+1,r21 ; *y_1(D), tmp44
13
std Z+2,r22 ; *y_1(D), tmp44
14
std Z+3,r23 ; *y_1(D), tmp44
15
ldi r22,0 ;
16
ldi r23,0 ;
17
movw r24,r22 ;
18
ret
Konkret: Verändern eines Objektes A (hier ein float) wird kein dazu
"inkompatibles" Objekt (hier long) ändern. Ausnahme ist char. Nun
überleg dir, was das für deinen Type-Punning Code bedeutet :-)
Peter Dannegger schrieb:> Ich hab mir auch mal Krücken für nen CAN-Bus machen müssen:> uint32_t swap_order( uint32_t val )> first> {> union{> uint32_t u32;> uint8_t u8[4];> } in, out;> in.u32 = val;> out.u8[0] = in.u8[3];> out.u8[1] = in.u8[2];> out.u8[2] = in.u8[1];> out.u8[3] = in.u8[0];> return out.u32;> }
Schon mal einen Gedanken an __builtin_bswap32 verschwendet?
http://gcc.gnu.org/viewcvs/trunk/libgcc/config/avr/lib1funcs.S?revision=193721&view=markup#l2723
Johann L. schrieb:> Konkret: Verändern eines Objektes A (hier ein float) wird kein dazu> "inkompatibles" Objekt (hier long) ändern. Ausnahme ist char.
Ok, verstanden.
> Nun überleg dir, was das für deinen Type-Punning Code bedeutet :-)
Hmm, ich hätte jetzt auf "nichts" getippt. In meinem Fall wird während
der kurzen Existenz des Zeigers ja nichts verändert.
Rolf Magnus schrieb:> void test2(float val)> {> foo(*(uint32_t*)&val);> }
Oder ist es einfach so, daß die Warnung vorgeschrieben ist?
Das sieht aber ganu anders aus, wenn die Funktion beim Aufruf bekannt
ist, etwa weil sie im gleichen Modul steh und / oder geinlint wird, per
Makro keingeklöppelt wird oder mit Optimierungen wie LTO übersetzt wird,
die modulübergreifend optimieren.
Johann L. schrieb:> Niemand bestreitet, daß der Code suboptimal ist, aber es ist definitiv> kein Fehler.>> Das "PR" steht weder für "Bug" noch für "Fehler", sondern für "Problem> Report".
Gut, im Sinne des C-Standards ist es natürlich kein Fehler, denn der
übersetzte Code macht was er soll. Dafür bin ich ja auch sehr dankbar.
>> Ich glaube/hoffe allerdings, dass dieser Fehler nur unter bestimmten,>> seltenen Umständen auftritt, sonst wäre das ja sicher schon mal in>> den Testfällen aufgefallen.>> Gesetzt den Fall, es gäbe solch einen Testfall in der GCC-Testuite [2].> Wem bitte sollte das Problem aussallen? Es werden ja noch nichtmal> regelmässig AVR-Tests gefahren, siehe [1]. Nach "avr" suchst du da> vergebens, zumindest die letzten 10 Monate.
Das ist schade. Ich habe von der Entwicklung des GCC leider keine
Ahnung, daher bitte nicht böse sein, wenn ich utopische Vorstellungen
habe. Ich hätte mir vorgestellt, dass es für jede Optimierung eine Reihe
von Testfällen gibt, die nur erfolgreich sind, wenn der Compiler die
Optimierung auf den Testfall korrekt anwendet. Einer davon hätte eben
sein können "Einen 16/32/64-Bit-Wert aus einzelnen Bytes per
Shiftoperator zusammensetzen. Der übersetzte Code darf maximal x Befehle
lang sein". Ich sehe aber schon, dass das wegen der unterschiedlichen
Architekturen, die der GCC unterstützt, schwierig ist.
> Ausserdem ist das Problem bekannt wie du an den genannten PRs siehst.>> Es ist allerdings so, daß es niemanden gibt, der sich um> avr-gcc kümmert. Weil es niemanden interessiert oder niemand Bock drauf> hat oder niemand weiß oder wissen will wie es geht oder weil niemand es> als wichtig genug erachtet, professionellen Support dafür zu> organisieren oder zu bezahlen — noch nicht mal Atmel.
Das ist erst recht schade und war mir so nicht bewusst. Dann ist auch
verständlich, dass solche "Probleme" keine hohe Priorität haben.
Fabian O. schrieb:> Johann L. schrieb:>> Gesetzt den Fall, es gäbe solch einen Testfall in der GCC-Testuite.>> Wem bitte sollte das Problem auffallen? Es werden ja noch nichtmal>> regelmässig AVR-Tests gefahren. Nach "avr" suchst du da>> vergebens, zumindest die letzten 10 Monate.>> [...] Ich hätte mir vorgestellt, dass es für jede Optimierung eine Reihe> von Testfällen gibt, die nur erfolgreich sind, wenn der Compiler die> Optimierung auf den Testfall korrekt anwendet. Einer davon hätte eben> sein können "Einen 16/32/64-Bit-Wert aus einzelnen Bytes per> Shiftoperator zusammensetzen. Der übersetzte Code darf maximal x Befehle> lang sein". Ich sehe aber schon, dass das wegen der unterschiedlichen> Architekturen, die der GCC unterstützt, schwierig ist.
Es gibt ja auch architekturabhängige Tests für das avr-Target:
http://gcc.gnu.org/viewcvs/trunk/gcc/testsuite/gcc.target/avr
Du kannst also einen Test schreiben und schauen, ob er durchgeht bzw.
die erwarteten Probleme korrekt rausfiltert. Wie GCC intern
funktioniert, brauchst du dafür nicht zu wissen.
>> Ausserdem ist das Problem bekannt wie du an den genannten PRs siehst.>>>> Es ist allerdings so, daß es niemanden gibt, der sich um>> avr-gcc kümmert. Weil es niemanden interessiert oder niemand Bock drauf>> hat oder niemand weiß oder wissen will wie es geht oder weil niemand es>> als wichtig genug erachtet, professionellen Support dafür zu>> organisieren oder zu bezahlen — noch nicht mal Atmel.>> Das ist erst recht schade und war mir so nicht bewusst. Dann ist auch> verständlich, dass solche "Probleme" keine hohe Priorität haben.
Die Priorität ist ziemlich wurscht. Nimm einfach diese Forum als
Beispiel und was passiert, wenn jemand eine gaaanz dringendes Problem
hat und eine (An)frage stellt wie:
"Achtung Wichtig! Habe Problem mit SLE 78!
Bitte schnell beantworten! DRINGEND!!!"
Glaubst du, nur weil jemand da wichtig-wichtig hinschreibt, wird hier
jeder alles stehen und liegen lassen, sich in den SLE 78 einarbeiten,
nur um sein Problem zu beheben?
Noch unrealistischer wird es, wenn Wichtig-Wichtig nicht nur eine Frage
hat, sondern Code haben will oder einen Schaltplan braucht und dafür
Entwicklung unternommen werden muss. Glaubst du, daß Wichtig-Wichtig
eine schnelle — ober überhaupt eine — Antwort bekommen wird, nur weil es
für ihn wichtig ist?
Auf AVR+GCC bezogen: Für eine Architektur gibt es die 1. Klasse, die 2.
Klasse und die 3. Klasse. 3x darfst du raten, on AVR drittklassigen
Support genießt oder nicht... U.a. ist AVR drittklassig weil:
1) Es keinen C++ Support gibt (libsupc++)
2) Es nicht genügend (konkret: überhaupt keine) Entwickler gibt,
die auftretende Probleme zeitnah beheben, so daß zB eine GCC-Release
nicht wegen AVR bis zu St. Nimmerlein verschoben werden muss.
3) avr-gcc ist nicht Standard-konform, z.B. double
Am C++ Support ist offenbar niemand interessiert — wobei ich unter
Interesse verstehe, daß sich diesbezüglich was tut in GCC.
Johann L. schrieb:> Es nicht genügend (konkret: überhaupt keine) Entwickler gibt
Ich kann deine Frustration diesbezüglich gut verstehen, man muss aber
auch sehen, dass leider die Einstiegshürde bei GCC sehr hoch ist was
einfach viele abschreckt. Das eine Firma wie Atmel lieber ihr eigenes
Süppchen kocht anstatt aktiv mitzuwirken ist dann natürlich doppelt
Schade.
Johann L. schrieb:> Es keinen C++ Support gibt (libsupc++)
Was "fehlt" den da? Ich dachte hier schon öfter C++ für AVR gesehen zu
haben.
Da ich in diesem Thread auch zum ersten mal mit "Aliasing" konfrontiert
wurde:
Darf der Compiler das grundsätzlich immer (und seit je her, also auch
vor C99), oder darf er das nur, wenn "strict aliasing" explizit
eingeschaltet ist?
Oder anders herum: Kann ich mich darauf verlassen, daß er das nicht so
optimiert, wenn ich "strict aliasing" nicht explizit eingeschaltet habe?
Läubi .. schrieb:> Johann L. schrieb:>> Es nicht genügend (konkret: überhaupt keine) Entwickler gibt>> Ich kann deine Frustration diesbezüglich gut verstehen, man muss aber
Keine Frustration, eher erstaunen darüber, welche Erwartungshaltung es
an avr-gcc gibt und welche Vorstellung darüber, wer sich wie intensiv
darum kümmert.
> auch sehen, dass leider die Einstiegshürde bei GCC sehr hoch ist was> einfach viele abschreckt.
Ja, stimmt wohl. Die Lernkurve des GCC ist irgendwo zwischen vertikal
und senkrecht unzusiedeln. Wenn GCC es nicht schafft, für (potentielle)
Entwickler so einfach handhabbar zu sein und einen so leichen Einstieg
zu bieten wie etwa LLVM, dann wird er m.E. über kurz oder lang nicht
damit LLVM mithalten können.
Nichtsdestotrotz wird avr-gcc breiter eingesetzt als LLVM, zumindest ist
das mein Eindrück, den ich auch nicht weiter belegen kann.
> Das eine Firma wie Atmel lieber ihr eigenes Süppchen kocht anstatt> aktiv mitzuwirken ist dann natürlich doppelt Schade.
Das ist ja Firmenpolitik von Atmel und deren Entscheidung wie sie mit
GCC umgehen. Immerhin war ihnen der AVR32 einen eigenen Port wert...
Die englische Wikipedia meint zum GCC:
"Several companies make a business out of supplying and
supporting GCC ports to various platforms, and chip manufacturers
today consider a GCC port almost essential to the success of an
architecture."
> Johann L. schrieb:>> Es keinen C++ Support gibt (libsupc++)>> Was "fehlt" den da? Ich dachte hier schon öfter C++ für AVR gesehen zu> haben.Bronco schrieb:> Darf der Compiler das grundsätzlich immer (und seit je her, also auch> vor C99), oder darf er das nur, wenn "strict aliasing" explizit> eingeschaltet ist?> Oder anders herum: Kann ich mich darauf verlassen, daß er das nicht so> optimiert, wenn ich "strict aliasing" nicht explizit eingeschaltet habe?
Eine C-Compiler erzeugt Code, der auf der realen Maschine (zB AVR) die
gleichen Seiteneffekte hat wie für die abstrakte Maschine spezifiziert.
Und dazu gehört eben auch, daß Änderung eines float keinen long ändern
kann etc.
In GCC können entsprechende Optimierungen mit -fno-strict-aliasing
deaktiviert werden.
Läubi .. schrieb:> Johann L. schrieb:>> Es keinen C++ Support gibt (libsupc++)>> Was "fehlt" den da? Ich dachte hier schon öfter C++ für AVR gesehen zu> haben.
avr-g++ ist i.W der nackte Compiler mit libgcc- und libc-Unterstützung
(wobei letztere kein Teil von GCC ist).
Konkret heißt das, daß nocht nichtmal der Sprachkern voll unterstützt
wird. Übersetz einfach mal
Wäre mir neu, daß der AVR Exceptions erzeugen kann. Er hat dafür weder
Vectoren noch Flags.
Z.B. ein Zugriff auf ungültigen Flash oder RAM oder ungültigen Opcode
führt er klaglos aus.
Peter
Peter Dannegger schrieb:> Wäre mir neu, daß der AVR Exceptions erzeugen kann. Er hat dafür weder> Vectoren noch Flags.
Hä?
> Z.B. ein Zugriff auf ungültigen Flash oder RAM oder ungültigen Opcode> führt er klaglos aus.
Ach solche Exceptions meinst du. Die haben rein gar nichts mit
C++-Exceptions zu tun.