Ineffiziente Berechnung von AVR-GCC

OP #7387764
Lesenswert?

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:

1
__attribute__((noinline)) void Mul16(uint8_t B8, uint16_t B16)
2
{
3
  TCNT1 = (uint8_t)(B16 >> 8) * B8;
4
}

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:

1
__attribute__((noinline)) void Mul8(uint8_t B8, uint16_t B16)
2
{
3
  TCNT1 = (uint8_t)((B16 >> 8) + 1) * B8;
4
}
1
00000064 <_Z4Mul8hj>:
2
  64:  7f 5f         subi  r23, 0xFF  ; 255
3
  66:  78 9f         mul  r23, r24
4
  68:  c0 01         movw  r24, r0
5
  6a:  11 24         eor  r1, r1
6
  6c:  9d bd         out  0x2d, r25  ; 45
7
  6e:  8c bd         out  0x2c, r24  ; 44
8
  70:  08 95         ret

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.

#7387835
Lesenswert?

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

OP #7388069
Lesenswert?

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.

1
__attribute__((noinline)) void Mul8(uint8_t B8, uint16_t B16)
2
{
3
  TCNT1 = (uint8_t)((B16 >> 8) + 0) * B8;
4
}
1
00000064 <_Z4Mul8hj>:
2
  64:  27 2f         mov  r18, r23
3
  66:  30 e0         ldi  r19, 0x00  ; 0
4
  68:  48 2f         mov  r20, r24
5
  6a:  42 9f         mul  r20, r18
6
  6c:  c0 01         movw  r24, r0
7
  6e:  43 9f         mul  r20, r19
8
  70:  90 0d         add  r25, r0
9
  72:  11 24         eor  r1, r1
10
  74:  9d bd         out  0x2d, r25  ; 45
11
  76:  8c bd         out  0x2c, r24  ; 44
12
  78:  08 95         ret

@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.

#7388083
Lesenswert?

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.
OP #7388163
Lesenswert?

@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.

#7388171
Lesenswert?

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.

#7388527
Lesenswert?

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.

#7388586
Lesenswert?

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.

#7388592
Lesenswert?

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.

#7388679
Lesenswert?

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:

1
Mul16:                                  ; @Mul16
2
; %bb.0:
3
        mul     r23, r24
4
        mov     r24, r1
5
        clr     r1
6
        mov     r18, r0
7
        clr     r19
8
        mov     r25, r24
9
        clr     r24
10
        or      r24, r18
11
        or      r25, r19
12
        sts     133, r25
13
        sts     132, r24
14
        ret
#7388698
Lesenswert?

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.

#7388754
Lesenswert?

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.

OP #7388866
Lesenswert?

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.

#7388889
Lesenswert?

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. :)

#7388902
Lesenswert?

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.

#7388903
Lesenswert?

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.

OP #7388953
Lesenswert?

@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.

Moderator Persönliche Seite #7388959
Lesenswert?

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.

#7389040
Lesenswert?

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.

#7389547
Lesenswert?

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_t b;
5
uint8_t a;
6

7
volatile uint8_t r;
8

9
template<typename A, typename B>
10
A Mul(const A a, const B b) {
11
    constexpr uint8_t shift = (sizeof(B) - sizeof(A)) * 8;
12
    return static_cast<A>(b >> shift) * a;
13
}
14

15
int main() {
16
    r = Mul(a, b);
17
    return r;
18
}

oder

1
    r = Mul<uint8_t>(a, b);

und bekommst das (fast) optimale Resultat:

1
        lds r25,b+1
2
        lds r24,a
3
        mul r24,r25
4
        mov r24,r0
5
        clr r1
6
        sts r,r24
7
        lds r24,r
8
        ldi r25,0

Dies ist etwas, was ich manchmal beobachte: wenn man generischen Code schreibt, produziert der gcc besseren Code.

#7389989
Lesenswert?

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<typename A, typename B>
2
B Mul(const A a, const B b) {
3
    const uint8_t shift = (sizeof(B) - sizeof(A)) * 8;
4
    return static_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!

Moderator #7390268
Lesenswert?

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.

OP #7390686
Lesenswert?

@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.

#7390696
Lesenswert?

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)

Moderator #7390885
Lesenswert?

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.

#7391065
Lesenswert?

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.

OP #7391328
Lesenswert?

@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:

1
__attribute__((noinline)) void Mul16(uint8_t I8, uint16_t I16)
2
{
3
  union
4
  {
5
    uint16_t I16;
6
    uint8_t A8[2];
7
  } U;
8
  
9
  U.I16 = I16;
10
  
11
  TCNT1 = U.A8[1] * I8;
12
}

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.

#7391330
Lesenswert?

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 ;-)

#7391332
Lesenswert?

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.

#7391339
Lesenswert?

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:

1
constexpr uint16_t mul(const uint8_t a, const uint16_t b) {
2
    const auto aa = std::bit_cast<std::array<uint8_t, 2>>(b);
3
    return aa[1] * a;
4
}

(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(unsigned char, unsigned int):
2
mul r23,r24      ;  tmp59, tmp58
3
movw r24,r0      ;
4
clr __zero_reg__
5
ret

Wer kein std::bit_cast hat, kann

1
uint16_t mul(const uint8_t a, const uint16_t b) {
2
    uint8_t aa[2];
3
    std::memcpy(aa, &b, 2);
4
    return aa[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.

OP #7392043
Lesenswert?

@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:

1
__attribute__((noinline)) void Mul16(uint8_t I8, uint16_t I16)
2
{
3
  TCNT1 = ((uint8_t*)&I16)[1] * I8;
4
}

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.

OP #7392134
Lesenswert?

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.

#7392140
Lesenswert?

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.

#7392603
Lesenswert?

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.

Beitrag #7392627 wurde von einem Moderator gelöscht.
OP #7392862
Lesenswert?

@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.

#7393589
Lesenswert?

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.

#7393601
Lesenswert?

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 ;-)

#7393712
Lesenswert?

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.

#7394295
Lesenswert?

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.

#7394327
Lesenswert?

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.

Persönliche Seite #7394401
Lesenswert?

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.

#7395132
Lesenswert?

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...

#7395193
Lesenswert?

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.

Moderator #7395234
Lesenswert?

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 :)

#7395328
Lesenswert?

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".

#7395442
Lesenswert?

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…

#7395885
Lesenswert?

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...

#7395892
Lesenswert?

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".

#7397360
Lesenswert?

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.

#7397377
Lesenswert?

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?

#7397428
Lesenswert?

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.

Moderator Persönliche Seite #7397530
Lesenswert?

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 …

#7397916
Lesenswert?

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.

#7404598
Lesenswert?

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").

Moderator #7404761
Lesenswert?

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.

#7405322
Lesenswert?

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 ...

Moderator Persönliche Seite #7405355
Lesenswert?

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.

#7405620
Lesenswert?

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.

OP #7406739
Lesenswert?

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.

#7406775
Lesenswert?

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...

Beitrag #7406955 wurde von einem Moderator gelöscht.
#7407543
Lesenswert?

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.

#7407772
Lesenswert?

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".

Beitrag #7407824 wurde von einem Moderator gelöscht.
#7407931
Lesenswert?

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.

#7407967
Lesenswert?

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.

Beitrag #7407991 wurde von einem Moderator gelöscht.
#7408013
Lesenswert?

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).

Beitrag #7408022 wurde von einem Moderator gelöscht.
#7408027
Lesenswert?

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).

#7408031
Lesenswert?

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

Beitrag #7408039 wurde von einem Moderator gelöscht.
#7408061
Lesenswert?

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...

Beitrag #7408065 wurde von einem Moderator gelöscht.
#7408069
Lesenswert?

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...

Beitrag #7408118 wurde von einem Moderator gelöscht.
Beitrag #7408124 wurde von einem Moderator gelöscht.
Beitrag #7408153 wurde von einem Moderator gelöscht.
Moderator Persönliche Seite #7408170
Lesenswert?

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.

Beitrag #7408211 wurde von einem Moderator gelöscht.
Beitrag #7408214 wurde von einem Moderator gelöscht.
Beitrag #7408420 wurde von einem Moderator gelöscht.
#7408448
Lesenswert?

Hallo, C-hater schrieb im Beitrag #7407931:

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

#7408530
Lesenswert?

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?

Beitrag #7408561 wurde von einem Moderator gelöscht.
Beitrag #7408562 wurde von einem Moderator gelöscht.
Beitrag #7408605 wurde von einem Moderator gelöscht.
Beitrag #7409263 wurde von einem Moderator gelöscht.
Beitrag #7409378 wurde von einem Moderator gelöscht.
Beitrag #7409381 wurde von einem Moderator gelöscht.
Dieser Beitrag ist gesperrt und kann nicht beantwortet werden.