Hallo Compilerexperten,
ich bin im Zuge der Optimierung des Codes eines AVR-GCC-Projekts auf ein
mir nicht erklärliches Verhalten des Compilers gestoßen, zu welchem ich
gerne Euren Rat einholen möchte. Um das Problem einfach beschreiben zu
können, habe ich die entsprechende Codepassage so weit wie möglich
simplifiziert, in eine eigene Funktion ausgelagert und ein Wegoptimieren
durch das Schreiben auf ein SF-Register verhindert:
Wie ersichtlich, soll einfach das High-Byte einer 16-Bit-Variable mit
einer 8-Bit-Variable multipliziert werden, was mit einer normalen
8-Bit-Multiplikation möglich sein sollte. Obwohl ich mit der
Typenumwandlung „(uint8_t)“ einer 8-Bit-Multiplikation Nachdruck
verleihe, produziert der Compiler eine 16-Bit-Multiplikation, behandelt
also „(uint8_t)(B16 >> 8)“ weiter als 16-Bit-Variable.
1
0000004e <_Z5Mul16hj>:
2
4e: 27 2f mov r18, r23
3
50: 30 e0 ldi r19, 0x00 ; 0
4
52: 48 2f mov r20, r24
5
54: 42 9f mul r20, r18
6
56: c0 01 movw r24, r0
7
58: 43 9f mul r20, r19
8
5a: 90 0d add r25, r0
9
5c: 11 24 eor r1, r1
10
5e: 9d bd out 0x2d, r25 ; 45
11
60: 8c bd out 0x2c, r24 ; 44
12
62: 08 95 ret
Ändere ich die Berechnung geringfügig, indem zum High-Byte zusätzlich
etwas addiert wird, so behandelt der Compiler den Multiplikator als
8-Bit-Variable und produziert eine erwartete 8-Bit-Multiplikation:
Ebenso wird eine 8-Bit-Multiplikation generiert, wenn die
16-Bit-Variable „B16“ weniger als 8 Mal nach rechts geschoben wird,
freilich nur mit der Typenumwandlung „(uint8_t)“.
Ich möchte Euch nun gerne Frage, ob Ihr für dieses Verhalten eine
Erklärung habt und wie eine schlanke 8-Bit-Multiplikation erreicht
werden kann ohne selbst mit Inline-Assembler die Berechnung
überschreiben zu müssen.
Das Projekt wurde im Übrigen für einen ATMega8 mit der
AVR8-Toolchain-Version 3.4.5.1522 und der GCC-Version 4.8.1 erstellt.
Ich bedanke mich für das Lesen meiner Frage und schon im Voraus für Eure
Antworten.
Beste Grüße,
Roland.
Roland .. schrieb:> Obwohl ich mit der> Typenumwandlung „(uint8_t)“ einer 8-Bit-Multiplikation Nachdruck> verleihe, produziert der Compiler eine 16-Bit-Multiplikation, behandelt> also „(uint8_t)(B16 >> 8)“ weiter als 16-Bit-Variable.
Das steht nunmal so in den Integer-Promotion-Rules von C. Vor der
Multiplikation werden die Operanden, deren Integertypen kleiner ist als
ein int, auf int gecastet.
Ansonsten fällt deine Frage, warum der Compiler irgend etwas so macht,
wie er es macht, in die große Kategorie „Ist halt so“.
Oliver
Hallo,
vielen Dank für Eure Antworten.
@Kaj G., Niklas G.
Das Umklammern des gecasteten Ausdrucks hatte ich bereits versucht und
erzeugt wie von Niklas geschrieben dasselbe Ergebnis.
@Rick
Ändert man den Summanden auf null, erhält man wieder eine
16-Bit-Multiplikation und das gleiche Ergebnis wie bei „Mul16“. Dasselbe
Ergebnis erhält man auch bei einer Addition von 256. Eine
8-Bit-Multiplikation wird jedoch auch bei einem Summanden von 257
produziert, der Compiler berücksichtigt scheinbar die Typenumwandlung im
Bezug auf die Addition.
@Oliver S.
Ja, das stimmt schon, merkt man auch bei 8-Bit-Vergleiche, die werden
auf 16-Bit erweitert. Allerding kann man mit der Typenumwandlung auf
8-Bit einen 8-Bit-Vergleich erzwingen.
1
if((uint8_t)a>1);
Es scheint ja auch im ursprünglichen Beispiel von „Mul8“ bei
Multiplikationen zu funktionieren, allerdings eben nur, wenn der
Multiplikation eine zusätzliche Berechnung vorausgeht.
Tja, die Kategorie „Ist halt so“ scheint es wirklich gut zu treffen.
Beste Grüße,
Roland.
Roland .. schrieb:> Allerding kann man mit der Typenumwandlung auf> 8-Bit einen 8-Bit-Vergleich erzwingen.
Nein.
In C findet immer eine Typpromotion nach int statt.
Daraus folgt, dass Berechnungen und Vergleiche immer mit mindestens
int-Breite stattfinden.
Dass der AVR-GCC dann trotzdem oft Code in 8-Bit ausgibt liegt daran,
dass er beweisen kann, dass das gleiche Ergebnis rauskommt wie bei der
Berechnung mit int.
Da das aber eine Optimierung ist, liegt es in der Natur solcher
Optimierungen, dass sie nicht immer 100% in allen Fällen greifen.
Du bist hier einfach nur auf eine solche fehlende Optimierung getroffen.
Da bleibt eigentlich nur:
- Code umstellen, dass hoffentlich eine Optimierung greift. Das ist oft
schwierig, wie wir hier ja nun sehen.
- Compilerupdate und hoffen, dass der neue Compiler das nun optimiert.
- Damit leben.
- Es in Inline-Assembly selbst schreiben.
Klaus R. schrieb:> Was macht -mint8?
Das ändert die ABI auf int = 8 Bit breit.
Ist meistens nicht zu empfehlen, wenn du irgendwelche Fremd-Libs
(Inklusive libc!) verwendest. Die avr-libc unterstützt das nicht.
Das ist mir klar, evtl. hätte ich es dazu schreiben sollen, dass diese
Option erhebliche Nebeneffekte hat. Habe nur grad keinen avr-gcc zur
Hand zum testen.
@MaWin O.
Vielen Dank für die Erklärung. Es war mir so nicht bekannt, dass der
Compiler nur dann auf eine 8-Bit-Berechnung umstellen kann, wenn die
Optimierung das ergibt. Ich war da der Auffassung, dass Operationen
immer in jener Breite durchgeführt werden, die von jener
Eingangsvariablen mit der höchsten Breite bestimmt wird. Bei Operationen
mit Konstanten ohne Längenangabe dann eben mit 16-Bit, wenn die
Variablenbreite kleiner ist.
Da dem scheinbar nicht so ist, wird das Beispiel hier dann wohl
tatsächlich einfach eine (triviale) nicht gefundene Optimierung.
@Klaus R.
Die Option „-mint8“ Löst das Problem tatsächlich, es wird in beiden
Fällen eine 8-Bit-Multiplikation generiert. Wie MaWin O. jedoch schon
geschrieben hat, gibt es dann diverse Warnungen und Probleme an anderen
Stellen, wenn damit generell mit 8-Bit gerechnet wird.
Gibt es vielleicht auch eine einfache Möglichkeit (eventuell ein
Attribut) „mint8“ nur auf einen bestimmten Codebereich oder einzelne
Sourcedateien anzuwenden?
Beste Grüße,
Roland.
Roland .. schrieb:> Gibt es vielleicht auch eine einfache Möglichkeit (eventuell ein> Attribut) „mint8“ nur auf einen bestimmten Codebereich oder einzelne> Sourcedateien anzuwenden?
Natürlich kannst du einzelne Sources mit und ohne mint8 übersetzen.
Aber da ist Vorsicht geboten, weil sich das ABI ändert und die
zusammengelinkten Module von anderen Voraussetzungen was int in
gemeinsam genutzten Symbolen und Funktionssignaturen angeht ausgehen.
Außerdem kannst du wie gesagt in einem mit mint8 übersetzen source
praktisch kein libc nutzen.
Ich halte das für eine exzellente Idee, wenn man es darauf anlegt in
Zukunft extrem schwer zu debuggende Bugs zu erzeugen.
Ja, das klingt im Großen und Ganzen nicht nach einer wirklich
praktikablen Lösung. Da ist wohl ein Ausweichen im Zweifel auf
Inline-Assembler eher eine Option, wenn auch dergleichen nicht ganz so
elegant anmutet.
Nun ja, wenn’s da wirklich auf jeden einzelnen Prozessorzyklus ankommt,
ist Assembler tatsächlich näher dran. Nur, wann muß das tatsächlich so
sein?
Oliver
Roland .. schrieb:> Ich war da der Auffassung, dass Operationen immer in jener Breite> durchgeführt werden, die von jener Eingangsvariablen mit der höchsten> Breite bestimmt wird.
Das stimmt im Prinzip auch, aber Mindestgröße ist dabei immer int. Das
ist quasi wie im Bierzelt mit dem Mindestverzehr. Wenn also ein Operand
kleiner ist als int, wird er erst mal auf int "angehoben" (das nennt
sich "integer promotion"). Damit ist natürlich auch das Ergebnis einer
Operation immer mindestens vom Typ int.
> @MaWin O.> Vielen Dank für die Erklärung. Es war mir so nicht bekannt, dass der> Compiler nur dann auf eine 8-Bit-Berechnung umstellen kann, wenn die> Optimierung das ergibt.
Im Prinzip sagt der Standard, dass Berechnungen nie kleiner als mit int
durchgeführt werden. Allerdings gibt es auch die "as-if"-Regel, die
besagt, dass der Compiler nicht zwingend alles exakt so machen muss, wie
angegeben. Er kann Dinge auch weglassen oder anders machen, solange er
sicherstellen kann, dass sich das zu beobachtende Verhalten ("observable
behavior") dadurch nicht ändert.
Möglicherweise lässt sich der Optimierer des Compilers durch Verwendung
der _fast-Typen erweichen, d.h. statt uint8_t uint_fast8_t verwenden.
Das widerspricht zwar den "promotion"-Regeln, aber ... wer weiß.
https://en.cppreference.com/w/c/types/integer
Harald K. schrieb:> Möglicherweise lässt sich der Optimierer des Compilers durch Verwendung> der _fast-Typen erweichen, d.h. statt uint8_t uint_fast8_t verwenden.
Nein, das funktioniert mit Sicherheit nicht. Das ist nur ein Typalias,
der zum Zeitpunkt der Optimierung schon lange aufgelöst ist.
Es ist generell überhaupt gar nicht möglich mit irgendeiner bestimmten
Codefolge in C eine ganz bestimmte Asm-Codefolge mit Sicherheit zu
produzieren. Das ist alles hochgradig compilerabhängig.
Das einzige worauf man sich verlassen kann ist, dass der generierte
Asm-Code sich unter den Bedingungen des C-Maschinenmodells (nicht der
realen Hardware!) so verhält als würde der geschriebene C-Code direkt
ausgeführt werden ("as-if").
Das beinhaltet aber nicht die Ausführungszeit des Codes und das
beinhaltet auch sonst keinerlei Hardwareeigenschaften (keine Interrupts,
keine Mehrkernsysteme).
Das C-Maschinenmodell ist abstrakt und vollkommen unabhängig von der
Zielhardware.
Damit aber im Fall der AVR-Architektur trotzdem noch halbwegs
vernünftiger Code herauskommt, gibt es im AVR-Backend noch einen
besonderen nachgeschalteten Optimierungsschritt, der Kenntnis über die
Zielhardware hat. Verlassen kann man sich darauf aber auch nicht.
Wenn man eine bestimmte Asm-Codefolge braucht, dann muss man sie in Asm
schreiben. Da bleibt keine andere Möglichkeit das sicher und stabil
(Compilerupdate) zu erreichen.
Nun, wenn ich mich recht irre, hieß es auch mal, daß der gcc eh' nie für
8-Bit-Architekturen entworfen wurde; in Anbetracht dieser Tatsche
liefert der schon verdammt guten AVR-Code.
Man müsste vergleichen, wie sich ein explizit für 8-Bit-Architekturen
geschriebener Compiler schlägt, also z.B. der IAR für AVR, oder eben der
XC8, den Microchip aber nur in kastrierter Ausführung dem Microchip
Studio beilegt. Oder der sdcc, aber der hat AVRs nie so recht
unterstützt.
Harald K. schrieb:> Nun, wenn ich mich recht irre, hieß es auch mal, daß der gcc eh' nie für> 8-Bit-Architekturen entworfen wurde;
Richtig.
> Man müsste vergleichen, wie sich ein explizit für 8-Bit-Architekturen> geschriebener Compiler schlägt, also z.B. der IAR für AVR, oder eben der> XC8, den Microchip aber nur in kastrierter Ausführung dem Microchip> Studio beilegt. Oder der sdcc, aber der hat AVRs nie so recht> unterstützt.
Es gäbe auch noch clang. Dessen daraus generierter Code sieht so aus:
Rolf M. schrieb:> Es gäbe auch noch clang.
clang ist aber kein explizit für 8-Bit-Architekturen entworfener
Compiler.
Und das sieht man an diesem generierten Code ja auch ganz gut ;)
Hallo,
warum möchtest du eine 8Bit Rechnung erzwingen? TCNT1 ist 16Bit breit.
Wenn das Ergebnis größer 255 sein sollte und er würde tatsächlich in
8Bit rechnen wäre das Ergebnis verfälscht. Warum der Versuch nach einem
Zwang in 8Bit? Außerdem rechnet er schon deswegen 16Bit weil B16 16Bit
breit ist.
Veit D. schrieb:> warum möchtest du eine 8Bit Rechnung erzwingen? TCNT1 ist 16Bit breit.
Wie er ausdrücklich geschrieben hat, diente die Zuweisung auf ein SFR
einzig dazu, dafür zu sorgen, dass der Code nicht komplett wegoptimiert
wird.
Capisce?
Veit D. schrieb:> warum möchtest du eine 8Bit Rechnung erzwingen?
Ergänzend: außerdem will er ja tatsächlich ein 16Bit-Resultat. Das ist,
was sich nach den Regeln der Mathematik (nicht denen der geistig extrem
behinderten C-Comiler) aus der Multiplikation zweier 8Bit-Werte ergibt.
Harald K. schrieb:> Rolf M. schrieb:>> Es gäbe auch noch clang.>> Wusste nicht, daß es davon einen AVR-Port gibt, aber man lernt ja nie> aus.
Der ist aber sehr experimental.
Der avr-gcc ist leider ab >4.6.4 in manchen Belangen schlechter
geworden. DAs haben wir hier schon öfter diskutiert. Der 13.0.1 ist
wieder etwas besser, aber leider nicht in diesem Fall.
Für 4.6.4 ergibt sich die gewünschte Optimierung zu einer reiner 8x8
Multiplikation.
Harald K. schrieb:> Rolf M. schrieb:>> Es gäbe auch noch clang.>> Wusste nicht, daß es davon einen AVR-Port gibt, aber man lernt ja nie> aus.
Es gibt keinen "AVR-Port" in dem Sinne. Das ist nicht wie bei gcc, wo du
für jede Zielarchitektur einen eigenen brauchst. Du benötigst nur einen
aktuellen clang, dann kann der automatisch alle Zielarchitekturen,
inklusive AVR. Allerdings braucht man halt noch die avr-libc dazu.
Hallo,
vielen Dank für die zahlreichen weiteren Antworten, Erklärungen und
Informationen.
@Oliver S.
Ich bin ja eigentlich der Meinung, dass man für ein Projekt irgendwie
den falschen Mikrocontroller gewählt hat, wenn man mit Assembler
versuchen muss, einzelne Zyklen zu optimieren. Es ist bei mir jedoch
schon vorgekommen, dass sich im Laufe des Projekts die (selbst
gesteckten) Anforderungen ändern und irgendwann stellt man fest, dass
die Rechenleistung nicht mehr so recht ausreicht. Weil man aber schon
viel Zeit investiert hat, möchte man auch nicht unbedingt alles wieder
ändern, weil die Rechenleistung ja „nur ganz knapp“ nicht reicht. Das
ist dann so ein Punkt, wo man sich gerne einen effizienten Code wünscht
und am Ende Teile in Assembler integrieren muss.
Bei diesem Projekt geht es um die Berechnung der Mischfarbe von Pixeln.
Im Prinzip ist es nicht so kritisch wie lange die Berechnung dauert, da
die Framerate sehr niedrig ist. Da jedoch in Summe sehr viele Pixel zu
berechnen sind, habe ich mir den entsprechenden erzeugten Maschinencode
angesehen und eben festgestellt, dass er nicht ganz so effizient ist.
@Rolf M., MaWin O.
Danke für die weitere Erklärung der Compilertechniken. Legt der
C-Standard dann auch die tatsächliche Breite der Größe „int“ fest?
Soweit ich das einmal gelernt habe, kann die Breite von „int“ anhängig
vom System variieren. Auf PC-Ebene – zumindest in Visual Studio – ist
ein „int“ 32-Bit breit. Eine Bitbreite von 16-Bit für den Typ „int“
scheint auch weit verbreitet zu sein. Wenn der C-Standard nun
vorschreibt, dass die Mindestgröße vom Typ „int“ sein muss, wird ja
eigentlich nicht die konkrete Bitbreite vorgeschrieben. Oder legt der
C-Standard auch fest, dass „int“ auch mindestens 16-Bit breit sein muss?
---
Interessantes Ergebnis des clang-Compilers. Immerhin merkt der Compiler
dass nur eine 8-Bit-Multiplikation nötig ist, verspielt es jedoch wieder
mit den Null-Veroderungen.
@Harald K.
Danke für diese Idee, wie MaWin O. schon prophezeit hat, erzeugt auch
der Fast-Typ denselben 16-Bit-Multiplikationscode.
@Veit D., c-hater
Wie c-hater schon geschrieben hat, dient die Zuweisung an „TCNT1“
lediglich dem Verhinder des Optimierens. Die eigentliche Berechnung
(Mischfarbenberechnung) ist umfangreicher, weshalb ich das Problem auf
das Nötigste verkürzt habe.
@Wilhelm M.
Das ist eine interessante Erkenntnis, dass die ältere Compilerversion
diese Optimierung findet. Sind dir auch Fälle bekannt, wo der ältere
Compiler ineffizienteres Ergebnis liefert als eine neuere Version?
Beste Grüße,
Roland.
Roland .. schrieb:> Legt der> C-Standard dann auch die tatsächliche Breite der Größe „int“ fest?
Nein. Das ist implementation defined, und beim AVR halt 16 Bit.
Oliver
Roland .. schrieb:> Legt der> C-Standard dann auch die tatsächliche Breite der Größe „int“ fest?
Nicht vollständig. Es sind relative Beziehungen zwischen den Typen
festgelegt.
> Soweit ich das einmal gelernt habe, kann die Breite von „int“ anhängig> vom System variieren.
Richtig.
> Auf PC-Ebene – zumindest in Visual Studio – ist> ein „int“ 32-Bit breit.
Das hat erst einmal nichts mit "PC" zu tun.
Das wird in der ABI definiert. (Application Binary Interface).
Die ist grob gesagt systemabhängig, aber ein System kann gleichzeitig
mehrere ABIs unterstützen.
Linux kann z.B. gleichzeitig die x64, i386 und x32 ABIs auf der PC
Plattform unterstützen.
> Oder legt der> C-Standard auch fest, dass „int“ auch mindestens 16-Bit breit sein muss?
Glaube schon. Bin mir aber nicht mehr ganz sicher.
Ist aber bestimmt nachzulesen in diesem Internet. :)
Roland .. schrieb:> produziert der Compiler eine 16-Bit-Multiplikation
Welcher denn eigentlich?
Da hat sich zwischen den einzelnen Versionen viel geändert.
Roland .. schrieb:> @Rolf M., MaWin O.> Danke für die weitere Erklärung der Compilertechniken. Legt der> C-Standard dann auch die tatsächliche Breite der Größe „int“ fest?
Er legt fest, dass int mindestens den Bereich -32767 bis +32767
unterstützen muss, was sich mit weniger als 16 Bit natürlich nicht
erreichen lässt. Außerdem muss seine Größe ein ganzzahliges Vielfaches
der Größe von char sein. Und dann gilt noch
sizeof(char)<=sizeof(short)<=sizeof(int)<=sizeof(long)<=sizeof(long
long).
Ein Compiler dürfte also laut C-Standard theoretisch also auch so
aufgebaut sein, dass char, short, int, long und long long alle die
gleiche Größe von z.B. 71 Bit haben. Allerdings wäre das eine eher
ungewöhnliche Wahl 😀.
> Soweit ich das einmal gelernt habe, kann die Breite von „int“ anhängig> vom System variieren.
Das ist richtig. Im Prinzip war es so gedacht, dass int nach Möglichkeit
die native Größe der jeweiligen CPU haben sollte (was aber vom Standard
nicht vorgeschrieben ist). Daher ist bei 16-Bit-Architekturen int 16 Bit
breit, bei 32-Bit-Architekturen entsprechend 32 Bit. Für 8-Bitter geht
es aber nicht, auf Grund der Mindestanforderung von int. Bei 64-Bittern
ist int in der Regel (bis auf ein paar Ausnahmen) immer noch 32 und
nicht 64 Bit, einfach weil einem sonst die Datentypen nach unten hin
ausgehen. Denn in C dürfen ja wie oben geschrieben nur char und short
kleiner sein als int, aber man möchte ja auch Datentypen für 8, 16, und
32 Bit unterstützen, bräuchte also drei Typen, die kleiner sind.
Hallo,
int wird im ANSI C mit Mindestgröße von 2 Byte festgelegt, was 16 Bit
signed sind. Minimum! Auf einem 8 Bit AVR sind int 16 Bit signed. Auf
deinem PC werden es sicherlich 32bit signed sein. Im Zweifel mit
sizeof() Datentypgröße anzeigen lassen. Möchtest du konkrete
Datentypengrößen verwenden, dann nimm int16_t oder int32_t usw., dann
sagt dessen Name was Programm ist.
Veit D. schrieb:> Möchtest du konkrete> Datentypengrößen verwenden, dann nimm int16_t oder int32_t usw., dann> sagt dessen Name was Programm ist.
Wobei das nicht von der Integer-Promotion entbindet, wie hier im Thread
schon erklärt.
Hallo,
korrekt. War mehr gedacht das er sich überlegen kann ob er int, long
oder int16_t, int32_t schreibt und weniger rätseln muss. Nur für den
Fall der Fälle.
@Oliver S., MaWin O., Rolf M., Veit D.
Danke für die Erläuterung. Das erklärt dann natürlich, warum sich für
8-Bit-Systeme die Breite von „int“ nicht an die Systembreite anpassen
und nur die Optimierung hier zu schlankerem Maschinencode führen kann.
@Jörg W.
Wie im Eingangsbeitrag am Ende angeführt habe ich das Projekt für einen
ATMega8 mit der AVR8-Toolchain-Version 3.4.5.1522 und der GCC-Version
4.8.1 erstellt. Beides mitgeliefert mit der IDE „Atmel Studio 6“ in
Version 6.2.1502 SP2.
Das ist wohl eine nicht mehr ganz taufrische IDE dessen Editor auch
teilweise mühsam ist, aber es läuft nun einmal soweit, man kennt das ja
vielleicht.
Was ich bisher vergessen habe zu erwähnen – wenn es denn relevant ist –
das Beispiel wurde als C++-Projekt angelegt und als Release mit der
Optimierungsstufe „Os“ übersetzt.
@Veit D.
Ich verwende bei Code für Mikrocontroller eigentlich ausschließlich
Integertypen mit festgelegter Breiter in der benötigen Größe in der
Hoffnung, so die beschränkte Rechenleistung Effizient nutzen zu können.
Dennoch Danke für den Ratschlag.
Beste Grüße,
Roland.
Roland .. schrieb:> GCC-Version 4.8.1
Die ist hornalt :-), aber interessanterweise zeigen auch aktuellere
Versionen (bei denen das Backend stark geändert wurde) ähnliches
Verhalten.
MaWin O. schrieb:> Wobei das nicht von der Integer-Promotion entbindet
Da gilt aber immer noch die "as if"-Regel. Da die zweite Multiplikation
mit 0 multipliziert, könnte der Compiler das durchaus wegoptimieren,
ohne die Promotion-Regeln zu verletzen.
Jörg W. schrieb:> Roland .. schrieb:>> GCC-Version 4.8.1>> Die ist hornalt :-), aber interessanterweise zeigen auch aktuellere> Versionen (bei denen das Backend stark geändert wurde) ähnliches> Verhalten.>
Wie oben schon geschrieben, ist diese missed-optimization seit 4.6.4
leider da.
Zu ähnlichen Fällen gibt es PR im bugzilla. Einige sind in 13.0.1
behoben, teilweise unvollständig.
Am besten ein PR eintragen, sofern noch keins existiert.
Johann kann bestimmt mehr dazu sagen.
Roland .. schrieb:> Was ich bisher vergessen habe zu erwähnen – wenn es denn relevant ist –> das Beispiel wurde als C++-Projekt angelegt und als Release mit der> Optimierungsstufe „Os“ übersetzt.
Prima.
Wenn es C++ ist, dann kannst Du es so schreiben:
1
#include<avr/io.h>
2
#include<stdint.h>
3
4
uint16_tb;
5
uint8_ta;
6
7
volatileuint8_tr;
8
9
template<typenameA,typenameB>
10
AMul(constAa,constBb){
11
constexpruint8_tshift=(sizeof(B)-sizeof(A))*8;
12
returnstatic_cast<A>(b>>shift)*a;
13
}
14
15
intmain(){
16
r=Mul(a,b);
17
returnr;
18
}
oder
1
r=Mul<uint8_t>(a,b);
und bekommst das (fast) optimale Resultat:
1
ldsr25,b+1
2
ldsr24,a
3
mulr24,r25
4
movr24,r0
5
clrr1
6
stsr,r24
7
ldsr24,r
8
ldir25,0
Dies ist etwas, was ich manchmal beobachte: wenn man generischen Code
schreibt, produziert der gcc besseren Code.
Oliver S. schrieb:> Wilhelm M. schrieb:>> Wenn es C++ ist>> Das war doch eigentlich schon seit:>> Z5Mul16hj>> klar.
Das stimmt. Hatte ich vergessen ...
Es ist natürlich totaler Blödsinn, was ich oben geschrieben haben: ich
bitte um Entschuldigung.
Richtig (das generische Äquivalent zur Funktion des TO) bzgl. des Typs
der Funktion muss es heißen:
1
template<typenameA,typenameB>
2
BMul(constAa,constBb){
3
constuint8_tshift=(sizeof(B)-sizeof(A))*8;
4
returnstatic_cast<A>(b>>shift)*a;
5
}
Und hier produzieren leider alle gcc > 4.6.4 denselben Murks.
Ich werde auch dazu ein PR erstellen, falls der TO das nicht schon
gemacht hat.
Sorry for the noise!
Wird es wohl, aber der stört halt kaum jemanden.
> Reported: 2012-10-04 18:27 UTC by Georg-Johann Lay> Status: UNCONFIRMED> Assignee: Not yet assigned to anyone
usw.
Die Hoffnung stirbt zwar zuletzt, aber in dem Fall ist die wohl doch
schon lange tot.
Oliver
Wilhelm M. schrieb:> Es scheint dieser Bug zu sein:>> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54816
Nicht ganz.
Im Beispiel von Johann wird der Shift als MULS implementiert, was einen
Zyklus länger dauert als die Variante mit drei LSL und ein zusätzliches
Register benötigt. Die Codegröße ist in beiden Fällen gleich. Beide
Varianten enthalten einen überflüssigen Befehl (CLR bzw. MOVW) und sind
deswegen sowieso nicht perfekt. Die Differenz von einem Taktzyklus
reicht offensichtlich nicht aus, das Problem als dringlich einzustufen.
Im Beispiel hier im Thread wird eine 16x16->16-Multiplikation nicht als
8x8->16-, sondern nur als 16x8->16-Multiplikation optimiert. Der Shift
(hier um 8 Bits) wird – anders als bei Johann – gar nicht explizit
ausgeführt. Deswegen haben die beiden Probleme nur wenig miteinander zu
tun.
Das Problem hier im Thread würde ich als schwerwiegender ansehen, da
hier die unvollständige Optimierung nicht nur mehr Zyklen (4), sondern
auch mehr Flash-Bytes (8) kostet,
Von daher gesehen hätte ein neuer Report zu diesem Problem vielleicht
eine höhere Chance auf Berücksichtigung.
@Wilhelm:
Falls du das Problem noch einmal einstellen möchtest, solltest du das
Template weglassen. Codebeispiele in PRs sollten sich auf das
Wesentliche konzentrieren, und da das Template nicht die Ursache des
Problems ist, verschleiert es das eigentlich Problem nur unnötig.
Yalu X. schrieb:> Falls du das Problem noch einmal einstellen möchtest, solltest du das> Template weglassen. Codebeispiele in PRs sollten sich auf das> Wesentliche konzentrieren, und da das Template nicht die Ursache des> Problems ist, verschleiert es das eigentlich Problem nur unnötig.
Das stimmt. Werde das korrigieren bzw. ein neuen PR aufmachen.
@Jörg W.
Stimmt, ich bin richtig erstaunt, welche hohe Versionsnummer der neueste
Compiler hat. Ich sollte wohl die gesamte Programmiersoftware erneuern
und gleich auf einen Open Source Editor umsteigen. Das Konfigurieren
schreckt mich da jedoch ein wenig ab.
@Wilhelm M.
Vielen Dank für Dein Engagement in dieser Sache und dem Erstellen eines
Fehlerreports.
Mich wundert es ja ein wenig, dass die fehlende Optimierung über sehr
viele Versionen hinweg noch keinem Aufgefallen ist oder zumindest
gestört hat, da eine derartige Berechnung meines Erachtens nicht so
Exotisch erscheint.
---
Gibt es eigentlich in der von der AVR8-Toolchain verwendeten
Bibliotheken eine Funktion, mit der sich das High-Byte extrahieren
lässt, oder wäre das dann ohnedies nur ein einfaches Shift-Makro?
Beste Grüße,
Roland.
Roland .. schrieb:> @Wilhelm M.> Vielen Dank für Dein Engagement in dieser Sache und dem Erstellen eines> Fehlerreports.> Mich wundert es ja ein wenig, dass die fehlende Optimierung über sehr> viele Versionen hinweg noch keinem Aufgefallen ist oder zumindest> gestört hat, da eine derartige Berechnung meines Erachtens nicht so> Exotisch erscheint.
Es war schon längst aufgefallen, ich war nur zu blöd zum Suchen:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66933
Wie schon gesagt, war Johann schon dran ;-)
Jedoch fehlte der Bug in der Liste hier:
https://www.mikrocontroller.net/articles/Avr-gcc_Bugs
(ich teste gerade den Patch dafür)
Roland .. schrieb:> Gibt es eigentlich in der von der AVR8-Toolchain verwendeten> Bibliotheken eine Funktion, mit der sich das High-Byte extrahieren> lässt, oder wäre das dann ohnedies nur ein einfaches Shift-Makro?
Aus C-Sicht wäre das schon nur Shift oder Division durch 256.
Roland .. schrieb:> Gibt es eigentlich in der von der AVR8-Toolchain verwendeten> Bibliotheken eine Funktion, mit der sich das High-Byte extrahieren> lässt, oder wäre das dann ohnedies nur ein einfaches Shift-Makro?
Hast Du doch verwendet: shift
Roland .. schrieb:> Mich wundert es ja ein wenig, dass die fehlende Optimierung über sehr> viele Versionen hinweg noch keinem Aufgefallen ist oder zumindest> gestört hat, da eine derartige Berechnung meines Erachtens nicht so> Exotisch erscheint.
Es gibt recht viele Fälle, wo der GCC Code mit 16-Bit-Operationen
erzeugt, wo auch 8-Bit-Operationen genügten. Das hängt u.a. damit
zusammen, dass der GCC ursprünglich für die auf unixoiden System
eingesetzten Prozessoren, also nicht für 8-Bit-Prozessoren vorgesehen
war.
Die Optimierung für 8-Bit-Prozessoren erfordert deswegen sehr viel
spezifische Detailarbeit, um alle denkbaren Fälle abzudecken. Johann hat
da schon viel Arbeit hineingesteckt mit deutlich sichtbarem Erfolg.
Trotzdem gibt es immer noch Kombinationen von Operationen, wo seine
Optimierungen nicht greifen.
Solange die Beschränkungen in der Optimierung nur wenige Taktzyklen oder
Programmbytes kosten, werden entsprechende PRs wohl als wenig dringlich
eingestuft oder gar nicht erst erstellt.
Roland .. schrieb:> Gibt es vielleicht auch eine einfache Möglichkeit (eventuell ein> Attribut) „mint8“ nur auf einen bestimmten Codebereich oder einzelne> Sourcedateien anzuwenden?
Ich wüßte nicht, wie... aber stelle mir auch die Frage, ob es wirklich
nötig ist, solche Mikrooptimierungen zu betreiben. Damit sich so etwas
lohnt, muß das Timing schon extrem kritisch und der betreffende Code
vergleichsweise oft aufgerufen werden, und in solchen Fällen fährt man
sicherlich mit Inline-Assembler besser. In allen anderen Fällen gelten
die Leitsätze von Donald E. Knuth ("premature optimization is the root
of all evil"), Kirk Pepperdine ("measure, don't guess"), und Kent Beck
("make it work, make it right, make it fast"). Oder, anders gesagt: in
den meisten Fällen sind solche Mikrooptimierungen kontraproduktiv, weil
sie die Les- und Wartbarkeit Deines Code verschlechtern, und nutzlos,
weil es in einem fertigen und vom Compiler optimierten Programm meist
ganz andere Stellen gibt, an denen eine Optimierung wesentlich
lohnenswerter ist.
Roland .. schrieb:> „mint8“ nur auf einen bestimmten Codebereich
Nein, denn letztlich ändert -mint8 das ABI. Wie schon geschrieben wurde,
bist du damit nicht nur inkompatibel zum C-Standard, sondern auch zur
avr-libc.
@Jörg W., Wilhelm M.
Ja, das ist schon klar, dass achtmal Rechtsschieben oder eine Division
durch 256 rechnerisch das High-Byte einer 16-Bit-Variale extrahiert. Auf
8-Bit-Maschinenebene wäre das dann logischer weise einfach das direkte
Auswählen des High-Bytes eines Registerpaares für weitere Berechnungen.
Ich dachte mir nur, es gibt vielleicht eine Inline-Assembler-Funktion,
die von der übergebenen Variable das höherwertige Register (wohl ein
ungerades Register) auf ein niederwertiges Rückgaberegister (wohl das
gerade Register 24) kopiert. Somit würde der Multiplikator dann schon in
einem geraden Register stehen, der Compiler dies als 8-Bit Multiplikator
erkennen und auf die, von den Integer-Promotion-Regeln vorgegebene,
Erweiterung auf 16-Bit verzichten.
Also klar, das macht so natürlich keinen Sinn, da der Compiler ja
einfach selbst zur weiteren Berechnung das obere Register des
Registerpaares direkt wählen kann. Es war im Prinzip nur eine Überlegung
der Optimierung etwas nachzuhelfen, da kleine Änderungen (wie eben das
zusätzliche Addieren einer Konstanten größer null) scheinbar die
Berechnungslogik so ändern, dass die Optimierung bei der Multiplikation
greift.
Aber ja, würde der Funktionsaufruf tatsächlich durchgeführt werden und
nicht als Inline ausgeführt werden, wäre die Zyklusersparnis nicht mehr
gegeben. Auch würden wohl unnötig viele Kopieroperationen hinzukommen
wodurch die Effizienzsteigerung kaum vorhanden wäre.
@Yalu X.
Ja, ich kann mir schon vorstellen, dass es nicht sehr einfach ist einen
Optimierungsfall zu erkennen. Oft wird zwar die Optimierung dieselbe
sein, aber die Ausgangssituation eine andere was wohl die Erkennung
erschwert.
@Sheeva P.
Das stimmt wohl. Die Lesbarkeit leidet unter so mancher Konstruktion in
der Hoffnung auf Optimierung schon sehr deutlich. Auch habe ich schon
festgestellt, dass die Kompaktheit des erzeugten Codes nicht wirklich
mit einer ausgeklügelten Optimierung der C-Formulierung steigt.
Beispielsweise diverse Schiebeoperation um möglichst effizient
Dividieren zu können werden vom Compiler meist auch selbst gefunden.
---
Im Zuge des Verfassens dieser Antwort habe ich mich dran Erinnert, dass
gelegentlich das Konstrukt der Union für das Auslesen einzelner Bytes
eines größeren Datentyps zweckentfremdet wird und mir die Frage
gestellt, ob das in diesem Fall den Compiler dazu bringen würde, die
Multiplikation mit 8-Bit durchzuführen:
Zu meiner Überraschung generiert der Compiler die erwartete Effiziente
8-Bit-Multiplikation:
1
0000004e <_Z5Mul16hj>:
2
4e: 78 9f mul r23, r24
3
50: c0 01 movw r24, r0
4
52: 11 24 eor r1, r1
5
54: 9d bd out 0x2d, r25 ; 45
6
56: 8c bd out 0x2c, r24 ; 44
7
58: 08 95 ret
Soweit ich das in Erinnerung habe, ist das Zweckentfremden einer Union
für das Manipulieren von Bytes von Variablen keine sonderlich gute Idee,
da wohl die Zuordnung der Speicherbereiche nicht garantiert werden kann.
In diesem Beispiel scheinen sich in der Union „U“ „I16“ und „A8[2]“
jedoch ein Registerpaar (R22/R23) zu teilen und mit „U.A8[1]“ der
Zugriff auf das High-Byte von „U.I16“ respektive „I16“ der
Eingangsvariable möglich. Durch die direkte Übergabe einer nun
8-Bit-Variable scheint die Optimierung des Compilers eine
8-Bit-Multipliktion für ausreichend zu erachten.
Beste Grüße,
Roland.
Roland .. schrieb:> Im Zuge des Verfassens dieser Antwort habe ich mich dran Erinnert, dass> gelegentlich das Konstrukt der Union für das Auslesen einzelner Bytes> eines größeren Datentyps zweckentfremdet wird und mir die Frage> gestellt, ob das in diesem Fall den Compiler dazu bringen würde, die> Multiplikation mit 8-Bit durchzuführen:
Das ist in C++ leider UB (in C wäre es legal). Du liest vom
nicht-aktiven Element der union.
Bevor nun alle in Entrüstung fallen: ja, ich weiß, dass der avr-g++ dies
wie gewünscht übersetzt ;-)
Roland .. schrieb:> @Jörg W., Wilhelm M.> Ja, das ist schon klar, dass achtmal Rechtsschieben oder eine Division> durch 256 rechnerisch das High-Byte einer 16-Bit-Variale extrahiert. Auf> 8-Bit-Maschinenebene wäre das dann logischer weise einfach das direkte> Auswählen des High-Bytes eines Registerpaares für weitere Berechnungen.
Das erkennt der Optimizer auch so, keine Sorge.
Roland .. schrieb:> Im Zuge des Verfassens dieser Antwort habe ich mich dran Erinnert, dass> gelegentlich das Konstrukt der Union für das Auslesen einzelner Bytes> eines größeren Datentyps zweckentfremdet wird und mir die Frage> gestellt, ob das in diesem Fall den Compiler dazu bringen würde, die> Multiplikation mit 8-Bit durchzuführen
Man kann aber Deine Idee aufgreifen und folgendes schreiben:
(Man könnte sagen: std::bit_cast ist der Ersatz für illegales
type-punning via union in C++. std::memcpy geht auch, aber nur zur
Laufzeit)
Das produziert ebenfalls:
1
mul(unsignedchar,unsignedint):
2
mulr23,r24;tmp59,tmp58
3
movwr24,r0;
4
clr__zero_reg__
5
ret
Wer kein std::bit_cast hat, kann
1
uint16_tmul(constuint8_ta,constuint16_tb){
2
uint8_taa[2];
3
std::memcpy(aa,&b,2);
4
returnaa[1]*a;
5
}
Der Unterschied hier ist, dass std::bit_cast auch constexpr ist.
Aber bitte: das ist von hinten durch die Brust ins Auge und sollte
niemals ernsthaft irgendwo verwendet werden.
Roland .. schrieb:> Zu meiner Überraschung generiert der Compiler die erwartete Effiziente> 8-Bit-MultiplikationOliver S. schrieb:> Ansonsten fällt deine Frage, warum der Compiler irgend etwas so macht,> wie er es macht, in die große Kategorie „Ist halt so“.
Oliver
Ich habe gerade den avr-gcc 13.0.1 mit einem Patch versorgt, und nun
produziert er nun wieder das optimale Ergebnis, wie auch schon vor
langer Zeit avr-gcc 4.6.4.
Der Patch ist hier zu finden:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109476
@Wilhelm M.
Vielen Dank für Deinen weiteren Einsatz sowie der Beispiele für einen
C++-Konformen Zugriff auf einzelne Bytes eines Datentyps . Wie ich
gesehen habe, hast Du in einem Beitrag „std:bit_cast“ hier vorgestellt.
In der auf meinem System installierten Compilerversion ist dies zwar
nicht verfügbar, aber der Alternative Umweg über „memcpy“ funktioniert.
In Anlehnung an diese Methode habe ich versucht, „memcpy“ in einfacher
Weise direkt mit wildem Pointer-Casting nachzuahmen:
Der Compiler erzeugt zwar damit auch eine einfache 8-Bit-Multiplikation,
allerdings ist der zusätzliche Overhead besonders umfangreich. Im
Gegensatz zu „memcpy“ wird das Array nicht in Registern gehalten,
sondern in das RAM ausgelagert, was natürlich enorm viel Effizienz
kostet:
1
0000004e <_Z5Mul16hj>:
2
4e: cf 93 push r28
3
50: df 93 push r29
4
52: 00 d0 rcall .+0 ; 0x54 <_Z5Mul16hj+0x6>
5
54: cd b7 in r28, 0x3d ; 61
6
56: de b7 in r29, 0x3e ; 62
7
58: 7a 83 std Y+2, r23 ; 0x02
8
5a: 69 83 std Y+1, r22 ; 0x01
9
5c: 9a 81 ldd r25, Y+2 ; 0x02
10
5e: 98 9f mul r25, r24
11
60: c0 01 movw r24, r0
12
62: 11 24 eor r1, r1
13
64: 9d bd out 0x2d, r25 ; 45
14
66: 8c bd out 0x2c, r24 ; 44
15
68: 0f 90 pop r0
16
6a: 0f 90 pop r0
17
6c: df 91 pop r29
18
6e: cf 91 pop r28
19
70: 08 95 ret
Erstaunlich finde ich im Übrigen die beiden Befehle „pop r0“, wo doch
„push r0“ zuvor nie ausgeführt wird, sowieso den mir nicht schlüssigen
„rcall“-Befehl.
Interessant, was sich im Beitrag Deines Bug-Reports tut, danke für das
Erstellen desselbigen. Hier scheint es doch ein Interesse zu geben und
womöglich wird der Patch in der nächsten Version integriert.
@Oliver S.
Exakt! :-)
Beste Grüße,
Roland.
Roland .. schrieb:> Erstaunlich finde ich im Übrigen die beiden Befehle „pop r0“, wo doch> „push r0“ zuvor nie ausgeführt wird, sowieso den mir nicht schlüssigen> „rcall“-Befehl.
rcall .0 schafft einen 2-Byte großen Stackframe, der dann im weiteren
benutzt wird. Die pops am Ende räumen den wieder ab.
gcc-Assembler-Magie ;)
Oliver
Ah, okay, also ein Funktionsaufruf an dieselbe Programmstelle, womit die
im Stack gespeicherte Rücksprungadresse als Datenspeicher
zweckentfremdet werden kann. Weil die Rücksprungadresse also geändert
wird, kann nicht mit „ret“ der Aufrufbefehl abgeschlossen werden,
sondern es wird mit „pop r0“ beendet.
Danke für die Erklärung. Ja, das ist wahrlich gcc-Assembler-Magie, sehr
gefinkelt ;-).
Beste Grüße,
Roland.
Roland .. schrieb:> In Anlehnung an diese Methode habe ich versucht, „memcpy“ in einfacher> Weise direkt mit wildem Pointer-Casting nachzuahmen:
In diesem Fall hast Du Glück und es kein UB: Du machst einen
pointer-cast und dereferenzierst. Dies ist normalerweise UB, sofern der
Zieltyp nicht (unsigned) char ist. Dies ist bei Dir der Fall (uint8_t)
und deswegen kein UB.
Heute macht man das aber per std::bit_cast. Dann ist es sogar constexpr
und auf jedenfall expressiver. Die Notlösung ist std::memcpy, was ja gar
kein Copy macht wie Du siehst, weil hier std::memcpy ein Spezialfall
ist, der von allen Compilern entsprechend behandelt wird.
Man sollte sich bei solchen Aktionen aber erstmal fragen, ob man nicht
versehentlich Mikro-Optimierung betreibt.
D.h. bringt die Optimierung an dieser Stelle wirklich den Meilenstein in
viel schnellerer Ausführung des Gesamtprogramms bzw. besonders
zeitkritischer Codeabschnitte oder eine bedeutende Senkung des
Flashverbrauchs.
@Wilhelm M.
Ein Pointer-Cast von „long“ auf „int“ wäre dann ein undefiniertes
Verhalten, obwohl ein „int“ weniger oder zumindest gleichviele Bytes
groß ist als der Datentyp „long“? Wird dann sozusagen nur garantiert,
dass an der gecasteten Adresse zumindest ein Byte Gültigkeit hat?
@Peter D.
Zweifelsohne ist das so. Wie schon geschrieben, spielt es in meinem Fall
keine allzu große Rolle. Ich habe mir eben jenen Teil des kompilierten
Programms angesehen, der in einer Schleife sehr oft aufgerufen wird
(Pixelberechnung) um zu sehen ob ich das Optimieren kann und
festgestellt, dass nicht nötiger Weise mit 16-Bit multipliziert wird.
Nachdem ich einen Fehler im Programmcode ausgeschlossen habe, wollte ich
einfach wissen wie es dazu kommt, was mir hier ja ausführlich erklärt
wurde.
Dank des Erstellens eines Fehlerberichts von Wilhelm wurde ja sogar eine
Optimierung des Optimierers angestoßen. Wenn dies in künftige
Compilerversionen einfließt ist das doch eine kleine Verbesserung.
Beste Grüße,
Roland.
Peter D. schrieb:> Man sollte sich bei solchen Aktionen aber erstmal fragen, ob man nicht> versehentlich Mikro-Optimierung betreibt.
Natürlich sollte man in der oben geschilderten Art seinen Quelltext
nicht modifizieren, das wäre ja Quatsch. Das waren auch nur Testcases,
um ggf. dem missed-optimization-bug auf die Spur zu kommen.
> D.h. bringt die Optimierung an dieser Stelle wirklich den Meilenstein in> viel schnellerer Ausführung des Gesamtprogramms bzw. besonders> zeitkritischer Codeabschnitte oder eine bedeutende Senkung des> Flashverbrauchs.
Der endgültige Patch für den avr-gcc, den ich jetzt teste, produziert in
Code mit einigen solcher 8x8 Multiplikationen ca. 10% kleineren Code und
natürlich auch schnelleren Code (wie stark sich das auswirkt, habe ich
noch nicht vermessen, doch das kannst Du Dir auch selbst ausrechnen
anhand des besseren Assembler).
Daher halte ich das nicht für eine Mikro-Optimierung. Ob und wann die
jetzt mainline geht, weiß ich nicht. Ist mir aber auch egal, da ich
meinen avr-gcc eh selbst erzeuge.
Roland .. schrieb:> @Wilhelm M.> Ein Pointer-Cast von „long“ auf „int“ wäre dann ein undefiniertes> Verhalten, obwohl ein „int“ weniger oder zumindest gleichviele Bytes> groß ist als der Datentyp „long“? Wird dann sozusagen nur garantiert,> dass an der gecasteten Adresse zumindest ein Byte Gültigkeit hat?
Das spielt gar keine Rolle, weil es einfach formal UB ist, wenn der
Zieltyp nicht char ist. Auch wenn es in der Praxis funktionieren wird
(principle of least surprise).
Wie oben schon gezeigt, ist std::memcpy() bzw. std::bit_cast die einzige
Möglichkeit, ein type-punning korrekt auszuführen. Allerdings gibt da
prinzipiell natürlich auch die Möglichkeit von trap-representations,
also nicht gültigen Bit-Kombinationen im Zieltyp. Kann man dies aber
ausschließen im konkreten Fall, so liegt kein UB mehr vor.
> Dank des Erstellens eines Fehlerberichts von Wilhelm wurde ja sogar eine> Optimierung des Optimierers angestoßen. Wenn dies in künftige> Compilerversionen einfließt ist das doch eine kleine Verbesserung.
Aktuell testen wir noch einen Patch. Dieser zeigt aktuell das richtige
Optimierungsverhalten und läuft auch in kompilizierten Fällen ohne ICE.
Die Codegrößer schrumpft dabei um ca. 10% bei Code, der viel solche
Sachen enthält. Von daher bestehen gute Chancen auf Integration in
mainline, und damit ist der avr-gcc > 13.0.1 dann wieder so gut wie
4.6.4. Na, wenn das mal nicht gut ist ;-)
Roland .. schrieb:> Ein Pointer-Cast von „long“ auf „int“ wäre dann ein undefiniertes> Verhalten, obwohl ein „int“ weniger oder zumindest gleichviele Bytes> groß ist als der Datentyp „long“? Wird dann sozusagen nur garantiert,> dass an der gecasteten Adresse zumindest ein Byte Gültigkeit hat?
Es ist verboten auf eine Variable über einem falschen (zum Variablentyp
unpassenden) Pointertyp zuzugreifen (Type Punning). Eine Ausnahme gibt
es nur für char.
Das heißt, wenn man long-pointer -> int-pointer castet, dann muss man
wieder zurück auf long-pointer casten bevor man den Zeiger
dereferenziert.
Ansonsten erzeugt man UB.
Man kann auch einen zeitkritischen Codeteil nach Assembler compilieren,
das *.S selber optimieren und alles zusammen linken.
Das finde ich deutlich lesbarer als inline Assembler.
Aufpassen, daß man das Compilieren nach Assembler wieder aus dem Make
rausnimmt, sonst wird das Optimierte wieder überschrieben.
Peter D. schrieb:> Man kann auch einen zeitkritischen Codeteil nach Assembler compilieren,> das *.S selber optimieren und alles zusammen linken.> Das finde ich deutlich lesbarer als inline Assembler.> Aufpassen, daß man das Compilieren nach Assembler wieder aus dem Make> rausnimmt, sonst wird das Optimierte wieder überschrieben.
Dafür programmiere ich aber nicht in C / C++. Optimierung ist eine der
vornehmsten Aufgaben eines Compilers. Und diese hier angesprochene
Optimierung ist doch wohl sehr erwartbar. Sonst reden doch hier auch
alle davon, wie toll die Compiler dies und das optimieren und das man
Kraut- und Rübencode schreiben können, der Compiler wird's schon
richten. Und dann bei so einem simplen Fall auf Assembler gehen: das ist
doch wohl nicht Dein Ernst.
Peter D. schrieb:> Aufpassen, daß man das Compilieren nach Assembler wieder aus dem Make> rausnimmt, sonst wird das Optimierte wieder überschrieben.
Das kann eigentlich nur passieren, wenn man ein lbödes OS hat, das nicht
zwischen *.s und *.S unterscheiden kann. GCC erzeugt von sich aus (etwa
mit -S oder zusammen mit -save-temps) nämlich nur *.s, aber nie *.S.
Abhilfe schafft dann, Endung *.sx zu verwenden. Oder Endung wie *.asm
nach gusto, aber dann mit -x assembler-with-cpp vor dem Modul.
Johann L. schrieb:> Das kann eigentlich nur passieren, wenn man ein lbödes OS hat, das nicht> zwischen *.s und *.S unterscheiden kann.
LOL
Es ist viel aufwendiger und komplizierter, eine
"case-independend"-Benamsung korrrekt umzusetzen. Jeder, der was vom
Programmieren versteht, kann mir da nur zustimmen.
Microsoft hat es geschafft (so ungefähr ab W2k dann endlich sogar
wirklich fehlerfrei).
Nur die wirklich blöden OS' können's bis heute nicht. Hängen also
entwicklungsmäßig in dieser Sache ungefähr ein Vierteljahrhundert
hinterher. Und das Schlimme ist: es ist keine Besserung absehbar, weil
es dann an allen Ecken und Enden heftigst knirschen würde im Gebälk...
C-hater schrieb:> Microsoft hat es geschafft (so ungefähr ab W2k dann endlich sogar> wirklich fehlerfrei).
Nein. Nicht wirklich. Das kann man gar nicht fehlerfrei umsetzen, weil
"Case" gar nicht global eindeutig definiert ist, außer vielleicht für
die Buchstaben a-z. Schon gar nicht zu der Zeit, wo MS mit dem Unsinn
angefangen hat.
Das ist ein absolut sinnloses Feature, was man gar nicht korrekt
implementieren kann.
Man kann es höchstens für die eigene Definition von "Case" korrekt
implementieren.
C-hater schrieb:> Microsoft hat es geschafft (so ungefähr ab W2k dann endlich sogar> wirklich fehlerfrei).
Auch Windows 10 und vermutlich Windows 11 können sich immer noch nicht
entscheiden, ob sie Dateinamen case-sensitiv oder case-insensitiv
behandeln sollen, wie folgende Beispiele zeigen:
Ich habe gerade in ein und demselben Verzeichnis erfolgreich drei
verschiedene Dateien angelegt, deren Namen sich nur in ihrer
Groß-/Kleinschreibung unterscheiden:
- straße.txt
- STRASSE.TXT
- STRAẞE.TXT
Dann sollten doch erst recht die beiden folgenden Dateinamen, die sich
nicht nur in der Groß-/Kleinschreibung, sondern auch in der Bedeutung
ganz klar unterscheiden, parallel existieren können:
- ahnen.txt
- Ahnen.txt
Da hat sich Windows aber gewehrt.
Ich halte das für einen schweren Bug :)
Yalu X. schrieb:> Ich habe gerade in ein und demselben Verzeichnis erfolgreich drei> verschiedene Dateien angelegt, deren Namen sich nur in ihrer> Groß-/Kleinschreibung unterscheiden:>> - straße.txt> - STRASSE.TXT> - STRAẞE.TXT
Nee, der mittlere fällt raus. Daß MS das Versal-SZ nicht korrekt
behandelt, das ist in der Tat ein Fehler, aber der Helvetizismus
"Strasse" ist nicht das gleiche wie "Straße".
Harald K. schrieb:> Nee, der mittlere fällt raus. Daß MS das Versal-SZ nicht korrekt> behandelt, das ist in der Tat ein Fehler, aber der Helvetizismus> "Strasse" ist nicht das gleiche wie "Straße".
STRASSE ist kein Helvetizismus, sondern die gängige Art in Deutschland,
das Wort "Straße" zu versalieren. So steht's auch im amtlichen Regelwerk
zur Rechtschreibung, wobei alternativ auch das ẞ erlaubt ist.
Das ẞ als "großes" ß hat sich bisher nicht wirklich durchsetzen können.
Es passt vom Schriftbild her auch nicht sonderlich gut zu den anderen
Großbuchstaben.
Jörg W. schrieb:> Mit der ineffizienten Berechung eines AVR-GCC hat das aber jetzt nicht> mehr viel zu tun …
Nun ja, ähm…
Harald K. schrieb:> Nee, der mittlere fällt raus. Daß MS das Versal-SZ nicht korrekt> behandelt, das ist in der Tat ein Fehler
Seit wann gibt's das in Unicode? Wenn ich mich richtig erinnere, noch
nicht sehr lange. Sprich: BUG ja, aber vermutlich eher temporärer Natur.
Wird irgendwann auch noch nachgepflegt werden. In manchen
skandinavischen Sprachen gibt's übrigens auch inzwischen Nachholbedarf
für MS. Da wurden ebenfalls einfach Unicode-Versalien ohne Sinn und
Verstand eingeführt. Also für Zeichen, die in normaler Schreibung
einfach niemals groß geschrieben werden (oder vielmehr wurden).
So ist das halt, wenn jemand anders die "Standards" setzt. Da kann man
nur hinterherjapsen. Der Unterschied ist: Linsux bemüht sich ja
nichtmal, sondern überläßt das einfach komplett den
Anwendungsprogrammierern...
Klar also, was die bevorzugen...
MaWin O. schrieb:> Das ist ein absolut sinnloses Feature
Nunja, mindestens eine Milliarde Windows-Benutzer weltweit sehen das
definitiv anders. Und vorher DOS-Benutzer, allerdings viel weniger als
eine Milliarde. Aber etliche Millionen werden es auch damals schon
gewesen sein, denen es nicht vermittelbar war, dass "FuckYou.Txt" was
anderes ein soll als "fuckyou.txt".
Ok, das dein Horizont mit unflätiger Sprache endet, wissen wir ja schon.
Zur Erweiterung des Horizonts:
GuteRinder.txt
GuterInder.txt
Der_gefangene_Floh.jpg
Der_Gefangene_floh.jpg
StauBecken.jpg
StaubEcken.jpg
UrInstinkt.txt
UrinStinkt.txt
etc.
@Wilhelm M., MaWin O.
Ich verstehe, danke.
---
Also eine Reduktion der Codegröße von 10% bei Programmen mit vielen
Berechnungen dieser Art finde ich dann schon beachtlich, danke für den
Einsatz.
Beste Grüße,
Roland.
Johann L. schrieb:> Ok, das dein Horizont mit unflätiger Sprache endet, wissen wir ja schon.
Und deiner endet offensichtlich bei einer weltweit derart irrelevanten
Sprache wie deutsch. Wieviele Leute sprechen und schreiben deutsch?
Wenn's hoch kommt, vielleicht 200 Millionen. Satte 2,5% der
Weltbevölkerung.
Nunja, wenn man die paar Latin-Sprachen dazu nimmt, bei denen die
Caseness eine ähnliche Rolle spielt wie im Deutschen, kommen wir schon
auf ein paar mehr Prozent. Relevant sind da eigentlich nur französisch,
spanisch und portugiesisch. Das bissel skandinavische Zeugs kann man
eher in den Skat drücken. Wie auch immer: großzügig aufgerundet sind wir
hier dann alles in allem bei vielleicht bei 10% der Weltbevölkerung.
Für die restlichen 90% ist das Windows/DOS-Konzept eine Erleicherung.
Genau deswegen gibt's das auch. MS wollte und will die Benutzer
glücklich machen, damit die MS kaufen. Allein der Erfolg des Konzepts
zeigt, dass sie das richtig analysiert haben. Schon damals, in den
frühen 1980ern. Da gab's Linux noch nicht mal. Nur die Unix-Vorgänger,
deren Anbieter das halt offensichtlich nicht so gut analysiert hatten.
Und die ein so beschissenes Filesystem-Konzept entwickelt haben, dass
sich sowas auch nachträglich nicht mehr dranstricken ließ. Das haben
dann heute noch relevante Sachen wie Linux oder Free/Open-BSD geerbt.
DAS ist der eigentliche Knackpunkt.
Und du kannst diese Tasachen nicht mit dem Verweis auf "unflätige
Sprache" meinerseits entkräften. Viele Leute sind durchaus intelligent
genug, um diese lächerliche Ausflucht als solche zu erkennen. Gerade im
konkreten Fall ist das nun wirklich sehr leicht.
C-hater schrieb:> Für die restlichen 90% ist das Windows/DOS-Konzept eine Erleicherung.> Genau deswegen gibt's das auch.
Ach Quatsch. Das gibt es, weil DOS sich das damals von CP/M so abgeguckt
hat, und das hat es so gemacht, weil damals noch nicht jeder Computer
überhaupt Kleinbuchstaben in seinem Zeichensatz hatte. Viele (inklusive
DOS) haben Kleinbuchstaben in Dateinamen gar nicht unterstützt. Also im
Prinzip der Gleiche Grund, aus dem es noch immer diese unsäglichen
Laufwerksbuchstaben gibt: Überbleibsel aus der Computer-Steinzeit.
> MS wollte und will die Benutzer glücklich machen, damit die MS kaufen.> Allein der Erfolg des Konzepts zeigt, dass sie das richtig analysiert> haben.
Klar, die Benutzer haben alle Windows nur deshalb auf ihren Computern,
weil das nicht zwischen Groß- und Kleinschreibung unterscheidet. Was
hast du geraucht?
Rolf M. schrieb:> Klar, die Benutzer haben alle Windows nur deshalb auf ihren Computern,> weil das nicht zwischen Groß- und Kleinschreibung unterscheidet. Was> hast du geraucht?
Aha, die idiotische Linux-Kloppertruppe auf Kriegspfad.
Nun denn: Was auch immer nun konkret die Windows (oder zuvor
DOS-Benutzer) dazu gebracht hat, eben diese OS zu wählen, ist reine
Spekulation.
Fakt ist hingegen, dass eben zu jeder Zeit seit den frühen 80ern des
vorigen Jahrhunderts diese Wahl durch die weit überwiegende Mehrheit so
getroffen wurde.
Zieht euch das einfach rein und denkt drüber nach, warum das so ist.
Natürlich sind die Gründe vielfältig, darüber brauchen wir nicht
diskutieren. Aber ganz sicher ist eins: Es gibt keine, wie auch immer
geartete Überlegenheit des Unix-Konzepts. Ganz sicher jedenfalls und
offensichtlich nicht aus Sicht der Nutzer...
Dazu kommt: MS hat das, was konzeptionell an Unix gut war, zumindest
später in den NT-Kernel übernommen hat. D.h.: das ist seit Jahrzehnten
auch Teil von Windows.
C-hater schrieb:> MS hat das, was konzeptionell an Unix gut war, zumindest später in den> NT-Kernel übernommen hat.
Das mit dem UNIX hatten sie paar Jahre vorher versucht, mit wenig Erfolg
(Xenix).
NT stammt von VMS-Entwicklern.
Dass sich Millionen von Nutzern nun ausgerechnet für case insensitive
entschieden hätten, halte ich für eine gewagte These: die haben
einfach genommen, was sie vorgesetzt bekommen haben. Hätte Microsoft
ihnen ein System vorgesetzt, das in den Dateinamen Groß- und
Kleinschreibung unterscheidet, hätten sie das genauso genommen.
Immerhin kann man in Windows inzwischen wenigstens ein (bspw.
vertipptes) FooBAr.txt in FooBar.txt umbenennen – lange Zeit ging das
nicht, denn schließlich gibt es die Zieldatei ja schon …
C-hater schrieb:> Aha, die idiotische Linux-Kloppertruppe auf Kriegspfad.
Beleidigungen, wie immer…
> Nun denn: Was auch immer nun konkret die Windows (oder zuvor> DOS-Benutzer) dazu gebracht hat, eben diese OS zu wählen, ist reine> Spekulation.
Du hast damit angefangen.
> Fakt ist hingegen, dass eben zu jeder Zeit seit den frühen 80ern des> vorigen Jahrhunderts diese Wahl durch die weit überwiegende Mehrheit so> getroffen wurde.
Nein, das ist kein Fakt, sondern Unsinn. Die "weit überwiegende
Mehrheit" hatte gar keine Wahl. Die musste das nehmen, was der
Hersteller programmiert hat.
> Dazu kommt: MS hat das, was konzeptionell an Unix gut war, zumindest> später in den NT-Kernel übernommen hat. D.h.: das ist seit Jahrzehnten> auch Teil von Windows.
Das meiste davon hat Microsoft allerdings nicht besonders gut umgesetzt.
Symlinks gibt es seit NT, sind aber bis heute unter Windows völlig
kaputt.
Rolf M. schrieb:>> Fakt ist hingegen, dass eben zu jeder Zeit seit den frühen 80ern des>> vorigen Jahrhunderts diese Wahl durch die weit überwiegende Mehrheit so>> getroffen wurde.>> Nein, das ist kein Fakt, sondern Unsinn. Die "weit überwiegende> Mehrheit" hatte gar keine Wahl. Die musste das nehmen, was der> Hersteller programmiert hat.
Ähemm.. Nö. Ich jedenfalls wurde zu keiner Zeit daran gehindert, z.B.
ein Linux parallel zu einem Windows oder auch als alleiniges OS auf
einem PC zu installieren (und habe das auch in all den Jahrzehnten immer
wieder getan). De facto nutze ich derzeit privat 2x Linux/Windows
Dual-Boot und 1x pure Linux, dienstlich (als Arbeitsrechner) 2x
Linux/Windows Dual-Boot. Die Server-Landschaft ist vielfältiger.
Dual-Boot gibt's da natürlich nicht, aber sowohl Windows als auch Linux
in jeweils etlichen Instanzen und sogar ein einsames BSD.
Deine Aussage ist also ganz offensichtlich ein freche und obendrein
überaus plumpe Lüge. Das kann man nicht beschönigen. Es gab zu keiner
Zeit einen objektiven Zwang, Windows zu benutzen. Die User hatten immer
die freie Wahl (Nunja, zuindest seitdem Linux überhaupt brauchbar
wurde). Das würde ich mal so ab ungefährt 1994 verorten (bzw:
eigentlich: "verzeitlichen").
C-hater schrieb:> Es gab zu keiner Zeit einen objektiven Zwang, Windows zu benutzen. Die> User hatten immer die freie Wahl (Nunja, zuindest seitdem Linux> überhaupt brauchbar wurde). Das würde ich mal so ab ungefährt 1994> verorten (bzw: eigentlich: "verzeitlichen").C-hater schrieb:> Fakt ist hingegen, dass eben zu jeder Zeit seit den frühen 80ern des> vorigen Jahrhunderts diese Wahl durch die weit überwiegende Mehrheit so> getroffen wurde.
Zwischen den frühen 80ern und 1994 liegt IMHO mindestens ein Jahrzehnt.
In den frühen 80ern hatte man auf PCs im Wesentlichen die Wahl zwischen
PC-DOS, MS-DOS und CP/M-86, viel mehr war da nicht.
Was die Groß-/Kleinschreibung von Dateinamen betrifft, hat Rolf also
völlig recht:
Rolf M. schrieb:> Die "weit überwiegende Mehrheit" hatte gar keine Wahl. Die musste das> nehmen, was der Hersteller programmiert hat.
Yalu X. schrieb:> Was die Groß-/Kleinschreibung von Dateinamen betrifft, hat Rolf also> völlig recht:
Nun, weder CP/M noch DOS kannten überhaupt das Konzept von Dateinamen.
Die hatten nur 8.3-Kürzel. Windows kannte vor 1993 (Windows NT) auch
keine Dateinamen, aber Windows NT brauchte noch gute acht Jahre, bis es
unter dem Namen "Windows XP" im Mainstream ankan. Bis dahin konnte man
ab etwa 1995 mit dem frickeligen Windows 95 immerhin auch schon in den
Genuss von Dateinamen kommen, die aber nur dann funktionierten, wenn man
auch 32-Bit-Programme verwendete.
Der Rest der Diskussion ist müßig, sich daran aufzugeilen, daß das
selbst verwendete Betriebssystem es natürlich richtig macht, während die
anderen es falsch machen hat was von praktiziertem Autismus, wenn nicht
schon von religiöser Intoleranz.
Wer aus der kindlich-analen Phase raus ist, schreibt seine Software so,
daß sie nicht darauf angewiesen ist, daß das Dateisystem bestimmte
Eigenschaften hat, sondern mit verschiedenen Gegebenheiten zurechtkommt.
Zum Beispiel auch Leerzeichen in Dateinamen oder Pfaden; es gibt
Softwareentwickler, die das können, und es gibt welche, die davon
gründlich überfordert sind. Die schreien dann natürlich laut rum, daß
das Fehler des dummen Anwenders ist, statt einzusehen, daß es nur ihre
eigene Unfähigkeit ist.
Und so bleiben uns weiterhin die schönen Flamewars erhalten, die wir
schon in den 80ern (Spectrum vs. C64, Atari vs. Amiga vs. Mac etc.pp.)
erleben durften.
Könnte man bloß Flamewars als Energiequelle nutzen ...
Harald K. schrieb:> Die hatten nur 8.3-Kürzel.
Auch das waren trotzdem Dateinamen. RSX11/M hatte 6+3 Zeichen, noch dazu
Radix-50 codiert, da konnte man gar keine Kleinbuchstaben überhaupt
reinschreiben, (bei CP/M konnte man es, am OS vorbei). Waren trotzdem
auch Dateinamen.
Frühe UNIX-Dateisysteme konnten auch bloß 14 Zeichen, erst seit BSD UFS
wurden es 255.
Harald K. schrieb:> Zum Beispiel auch Leerzeichen in Dateinamen oder Pfaden; es gibt> Softwareentwickler, die das können, und es gibt welche, die davon> gründlich überfordert sind.
Es gibt aber auch diejenigen, die es bewusst vermeiden, weil sie wissen,
dass es andere Software gibt, die damit Probleme hat. Und dann gibt es
die, denen das egal ist und die es deshalb einfach den Anwender ausbaden
lassen, weil sie eben in ihrer "kindlichen" Eigenschaft lieber stolz
präsentieren wollen, dass sie mit Leerzeichen zurechtkommen.
Rolf M. schrieb:> Und dann gibt es> die, denen das egal ist und die es deshalb einfach den Anwender ausbaden> lassen, weil sie eben in ihrer "kindlichen" Eigenschaft lieber stolz> präsentieren wollen, dass sie mit Leerzeichen zurechtkommen.
Welches Problem verursacht eine Software, die mit Leerzeichen in
Pfaden/Dateinamen zurechtkommt, bei den Anwendern?
Vielen Dank Dir Wilhem M. für das Anstoßen der Fehleranalyse und dem
Mitarbeiten am Patch, sowie natürlich an Roger Sayle, Richard Biener und
Segher Boessenkool, die sich des Problems gleich angenommen haben.
Ich finde es schon großartig, dass diese Modifikation nun in so kurzer
Zeit Einzug in die nächste Compilerversion hält, zumal das Problem ja
nur eine fehlende Optimierung betrifft. Eine wirklich großartige
Open-Source-Community.
Beste Grüße,
Roland.
Roland .. schrieb:> Vielen Dank Dir Wilhem M. für das Anstoßen der Fehleranalyse und dem> Mitarbeiten am Patch, sowie natürlich an Roger Sayle, Richard Biener und> Segher Boessenkool, die sich des Problems gleich angenommen haben.>> Ich finde es schon großartig, dass diese Modifikation nun in so kurzer> Zeit Einzug in die nächste Compilerversion hält, zumal das Problem ja> nur eine fehlende Optimierung betrifft. Eine wirklich großartige> Open-Source-Community.
Nunja, bei den AVR8 als Target bin ich sehr froh, dass ich keine
derartigen Abhängigkeiten von einem wohlwollenden, weltweit verteilten
Entwicklerkollektiv habe...
Asm rules...
Zweifelsohne hat es schon einen Reiz, wenn man in Assembler programmiert
und so das Maximum an Effizienz aus dem Controller herausholt, was oft
auch mit sportlichem Ehrgeiz einhergeht. Je nach Umfang des Projekts
kann es mitunter aber auch Mühsam und Unübersichtlich sein.
Roland .. schrieb:> Je nach Umfang des Projekts> kann es mitunter aber auch Mühsam und Unübersichtlich sein.
Unser C-Hasser kann sich ja mal daran versuchen:
https://github.com/threeme3/usdx
Mal sehen, wie viele Jahre es dauert, bis er das in Assembler
geschrieben hat …
Roland .. schrieb:> Zweifelsohne hat es schon einen Reiz, wenn man in Assembler programmiert> und so das Maximum an Effizienz aus dem Controller herausholt
Du schreibst das so, als würde das reine Programmieren in Assembler das
bereits implizieren. Tut es nicht, man kann natürlich auch in Assembler
problemlos ineffizienten Code schreiben. Und die meisten werden, auch
wenn das, was sie schreiben, nicht direkt ineffizient ist, noch weit
entfernt sein von dem, was wirklich das mögliche Maximum ist, viel mehr,
die meisten werden noch nicht mal gegen einen anständigen Compiler
anstinken können.
Harald K. schrieb:> Welches Problem verursacht eine Software, die mit Leerzeichen in> Pfaden/Dateinamen zurechtkommt, bei den Anwendern?
Lies meinen Beitrag nochmal. Ich sprach von Problemen, die durch
Software ausgelöst wird, die damit nicht zurechtkommt.
Du hast das hier geschrieben:
Rolf M. schrieb:> Und dann gibt es die, denen das egal ist und die es deshalb> einfach den Anwender ausbaden lassen, weil sie eben in ihrer> "kindlichen" Eigenschaft lieber stolz präsentieren wollen,> dass sie mit Leerzeichen zurechtkommen.
Das bedeutet, daß diejenigen, die "stolz präsentieren wollen, daß sie
mit Leerzeichen zurechtkommen", es "den Anwender ausbaden lassen".
Jörg W. schrieb:> https://github.com/threeme3/usdx>> Mal sehen, wie viele Jahre es dauert, bis er das in Assembler> geschrieben hat …
Monate ja, ohne Zweifel, aber sicher keine Jahre. Und nach diesen
Monaten bin ich ziemlich sicher, dass ich etwas geschaffen hätte, was
diesen C-Kram bezüglich der Performance sehr deutlich in den Schatten
stellt.
Ich brauche nur kurz in das Kompilat zu schauen, um die erheblichen
Redundanzen selbst bei -O3 zu sehen. Das könnte ich mit an Sicherheit
grenzender Wahrscheinlichkeit deutlich schlagen.
Dazu kommt noch: du schummelst! Der Code enthält unzählige in (inline-)
Asm programmierte Schnipsel und umschifft nur allein damit die größten
Schwachen des Compilers. Wäre auch dieser ganze Kram in Plain-C
programmiert, wäre das Ergebnis noch viel, viel verheerender.
Aber: Ich habe wirklich kein Interesse an Amateurfunk. Und für
Anwendungen derselben Algorithmen in Bereichen, die mein Interesse
erwecken, ist ein AVR8 schlicht nicht schnell genug.
Harald K. schrieb:> Das bedeutet, daß diejenigen, die "stolz präsentieren wollen, daß sie> mit Leerzeichen zurechtkommen", es "den Anwender ausbaden lassen".
Du hast leider vergessen, den entscheidenden Teil mit zu zitieren:
Rolf M. schrieb:> Es gibt aber auch diejenigen, die es bewusst vermeiden, weil sie wissen,> dass es andere Software gibt, die damit Probleme hat.
Rolf M. schrieb:> Du hast leider vergessen, den entscheidenden Teil mit zu zitieren:
Das machts nicht besser. "Es gibt auch diejenigen" ... "und dann gibt es
die" ... ist eine klassische Gegenüberstellung von Gegensätzen.
Du hast Dich da missverständlich ausgedrückt, aber es ist ja schön, daß
wir die Sache inhaltlich auf die gleiche Weise sehen.
Damit können wir die Leerzeichen jetzt wohl ruhen lassen.
C-hater schrieb:> erheblichen Redundanzen selbst bei -O3
Klar – weil du -O3 schlicht nicht verstanden hast.
Musst du auch nicht (da du ja keine Compiler benutzen willst), aber über
Sachen, die man nicht verstanden hat, sollte man nicht herum unken.
Es hat einen Grund, dass -Os bei MCUs die beliebteste
Optimierungseinstellung ist.
Jörg W. schrieb:> Klar – weil du -O3 schlicht nicht verstanden hast.
Na dann: Klär mich auf!
Was macht diese "Optimierung" genau, was ist ihr Ziel? Und warum muß man
sich den Code des Compilers anschauen, um das heraus zu finden? Sowas
sollte klar und eindeutig dokumentiert sein.
Ein Werkzeug ohne brauchbare Dokumentation ist lebensgefährlich. Darauf
sollte sich niemand verlassen müssen. Ich brauch' das auch nicht, weil
dieses "Werkzeug" halt nur als Quelle der Erheiterung verwende
(zumindest was AVR8 betrifft). Allerdings: die bezüglich AVR8 gewonnenen
Erkenntnisse haben mich auch vieles gelehrt bezüglich anderer
Zielarchitekturen, wo ich diesen Scheiß aus praktischen Gründen
tatsächlich oft verwenden muss.
Das Fazit ist: Brauchbar gut sind Compiler allenfalls im Mainstream. Und
selbst da hinken sie der Entwicklung der Hardware immer deutlich
hinterher und sind auch ansonsten suboptimal, weil sie immer ihr eigenes
ABI einhalten müssen. Genau dieses konstante ABI ist aber der Pitfall,
der ihnen viele der in Asm mögliche Optimierungen unmöglich macht. Und
dies ist wiederum beileibe nicht auf AVR8 beschränkt. Hat man z.B. bei
ARM genauso.
Diese zwei Architekturen sind mein primäres Interesse im kleinen
µC-Bereich. Der PC-Kram ist hingegen heute so schnell, dass die
(gegenüber dem Optimum) schlechte Compiler-Performance kaum noch
relevant ist. Hier ist das Problem eher die immer weiter ausuferende
Verwendung immer fetterer Frameworks mit noch höherer Abstraktionslevel
(betrifft allerdings auch die "dicken" ARMs).
Jörg W. schrieb:> Es hat einen Grund, dass -Os bei MCUs die beliebteste> Optimierungseinstellung ist.
Diesen Satz hatte ich komplett überlesen. Das soll aber wohl ein Witz
sein? Klar kann es in wichtig sein, ob eine Anwendung überhaupt noch in
in den Flash eines µC passt. Wenn man wenigstens 10000er Stückzahlen der
Lösung produziert und der (bezüglich Flash-Size) nächstgrößere µC der
Produktlinie exorbitant mehr kostet oder es gar keinen in der Linie mit
größerem Flash mehr gibt. Ansonsten:
Viel wichtiger ist aber im Allgemeinen doch wohl, ob die Anwendung ihren
Job überhaupt erledigen kann. Und das von dir gewählte Beispiel ist so
eine Sache, bei der wohl eher die Rechenzeit der begrenzende Faktor ist.
Das ist bei Signalverarbeitung meist so. Und der übliche Ausweg ist
halt: Tabellen. Macht der Compiler bei Speed-Optimierung so und
natürlich auch der kompetente Asm-Programmierer. Der kann es nur viel
besser (wenn er hinreichend gut ist).
C-hater schrieb:> Was macht diese "Optimierung" genau, was ist ihr Ziel? Und warum muß man> sich den Code des Compilers anschauen, um das heraus zu finden? Sowas> sollte klar und eindeutig dokumentiert sein.
Das ist es. -O3 opfert Programmgröße für Speed. Loop-unrolling, massives
inlining, usw. Nur ist das alles auf einem AVR ziemlich wirkungslos, da
bei dem durch den (fast) echten RISC-Befehlssatz und wegen fehlenden
Späßchen wie Sprungvorhersagen, mehrstufigen Caches, und all den anderen
Hardwarespirenzchen größerer Prozessoren kürzerer Code halt doch
schneller ist als längerer.
Daher nimmt man da -Os, und alles wird so gut, wie es halt geht.
Oliver
Oliver S. schrieb:> Das ist es. -O3 opfert Programmgröße für Speed.
Das war auch, was mir bekannt ist. Umso überraschender war für mich der
Einwurf von Jörg W. Seines Zeichens Moderations-Macht-Missbraucher
(nicht in diesem Thread (zumindest noch nicht, ich warte aber förmlich
darauf), aber in anderen, das kann ich detailliert nachweisen!).
-O3 sollte also theoretisch das Maximum an Speed aus dem Compiler
herausholen. Damit ist es das, mit dem sich mein Asm-Code vergleichen
müsste. Genau deswegen habe ich auch das mit eben dieser Option
übersetzte Kompilat als Referenz benutzt.
Und da kann ich eben ganz klar sagen: das könnte ich ganz sicher
deutlich besser als es der Compiler kann.
Wie eigentlich jede AVR8-Sache. Der Compiler ist für AVR8 als Target
halt einfach ziemliche Scheiße. Da spielen mehrere Sachen hinein. Zum
einen ist jeder C-Compiler bei AVR8 als Zielarchitektur gleich doppelt
behindert. Die mögen weder Harvard-Architektur noch native
8Bit-Verarbeitung. Was die wollen, um einigermaßen effizient zu sein
ist: Die Verabeitung passiert mit (mindestestens) 16Bit. Und es gibt nur
einen Addressraum. Und: Es gibt keine Nebenläufigket.
All das ist halt bei typischen AVR8-Anwendungen nicht gegeben. Deswegen
ist es gerade bei dieser Architektur so überaus einfach, als
Asm-Programmierer sehr deutlich besser zu sein als der Compiler...
C-hater schrieb:> Der Compiler ist für AVR8 als Target> halt einfach ziemliche Scheiße.
Wenn man das aus deiner Ausdrucksweise ins normale Deutsch übersetzt,
stimmt das in etwa. gcc ist für AVR8 nicht perfekt, aber zumindest
brauchbar.
Oliver
Oliver S. schrieb:> Wenn man das aus deiner Ausdrucksweise ins normale Deutsch übersetzt,> stimmt das in etwa. gcc ist für AVR8 nicht perfekt, aber zumindest> brauchbar.
Far away from to be perfect.
Klar: Um ein paar LEDs blinkenzu lassen oder primitive state-machines
mit ein wnig Input und Output zu implementieren reicht es. Und mehr hat
der Herr Jörg W. auch niemals gezeigt...
Das von ihm selber gewählte Beispiel für mehr ist ja bezeichnenderweise
auch nicht von ihm...
C-hater schrieb:> Damit ist es das, mit dem sich mein Asm-Code vergleichen müsste.
Du hast den Vergleich aber mit der Größe gemacht, nicht der
Geschwindigkeit: "erheblichen Redundanzen selbst bei -O3".
Daher die Aussage: diese "Redundanzen" sind bei -O3 schlicht Absicht,
nur bringen sie beim AVR aufgrund seiner Architektur kaum was. Deshalb
nimmt man diese Stufe dort schlicht nicht. Wenn du auf Redundanzen
vergleichen willst, dann nimm -Os: "Zusätzlich (zu Stufe 1) noch alle
die Optimierungen von Stufe 2, die (normalerweise) den Code nicht größer
werden lassen", "optimized for size" eben, nichts anderes heißt es.
Dass du sowieso alles viel besser kannst, wussten wir auch so schon. Nur
ist halt der Aufwand dafür ein Mehrfaches. Wenn man sich das leisten
kann: kein Problem, mach doch. Da ich für den nicht genutzten Flash von
Microchip nichts zurück bekomme, leiste ich mir den Aufwand in der Regel
eher nicht.
Hallo,
C-hater schrieb:> Das könnte ich mit an Sicherheit grenzender Wahrscheinlichkeit> deutlich schlagen.
Ich lese hier immer wieder das die heutigen Compiler Maschinencode in
einer Qualität erzeugen, der nur schwer von handgeschriebenen Assembler
übertroffen werden kann. Jetzt behauptest du genau das Gegenteil.
Zeig doch mal an einem typischen, nicht zu simplen, Beispiel wo ein
handassemblierter Maschinencode den C-Compiler signifikant schlägt.
rhf
Selbstüberschätzung ist gerade bei Verfechtern der
Assemblerprogrammierung gar nicht so selten. Im Gegensatz zu "moby"
traue ich dem Programmiersprachenhasser aber wenigstens etwas mehr
Kompetenz zu. Vor allem kann er besser fluchen.
Und was wäre ein guter Programmierer ohne anständiges Gefluche?
Da Moby den Thread nur noch zum permanenten Wiederholen seiner
Auslassungen missbraucht: hier ist eh alles geschrieben worden
inzwischen, was zum Thema beiträgt.