Hallo Leute,
es gibt ja so die Regeln beim Optimieren, dass man nicht schlauer sein
sollte als der Compiler und der Compiler schon so optimiert.
meine Frage:
soll ich jetzt lieber so schreiben:
1
#define LOOPS (10)
2
for(intloop=0;loop<LOOPS;loop++){
3
// loop variable nicht verwendet
4
}
oder eher so (handoptimiert:
1
#define LOOPS (10)
2
for(intloop=LOOPS;loop!=0;loop--){
3
// loop variable nicht verwendet
4
}
Mir ist es eher egal. Lesbarer finde ich die erste Variante.
Wann aber im Code durch unterschiedliche Programmierer mal so mal so
gemacht wird, finde ich das eher lästig und das stört mich schon.
Mit AVR gcc 4.6.4 und -O1 getestet im Compiler Explorer.
Bei -O3 ist das Gleiche mit ausgerollter Schleife.
Denke die Frage ist damit geklärt, der Compiler macht ein interessanten
Job. Hab mich da auch immer gefragt, wie man Schleifen am Besten für den
Compiler vorbereitet. Jetzt kenn ich die Antwort.
Adib schrieb:> Lesbarer finde ich die erste Variante.
Das kann man nicht verallgemeinern. Viele finden die 2. Variante
lesbarer.
Ich hab mit Assembler angefangen (8051), da ist quasi die 2. Variante
der DJNZ-Befehl.
Als ich mit C angefangen hatte, habe ich mich mit der 1. Variante oft um
+/-1 verzählt. Ich mußte immer erst überlegen, ist der Startwert 0 oder
1 und muß ich auf < oder <= vergleichen.
Bei der 2. Variante gibt es keine Fehlermöglichkeit, die Anzahl
Durchläufe ist der Initialwert.
Und falls die Anzahl keine Konstante, sondern eine Variable ist, muß man
nicht erst überlegen, ob sie sich während der Schleife ändern könnte.
Wenn der Compiler das nicht mehr erkennen kann, muß er für die erste
Variante den längeren Code erzeugen.
> Bei der 2. Variante gibt es keine Fehlermöglichkeit, die Anzahl> Durchläufe ist der Initialwert.
Nicht ganz, zur allgemeinen Erheiterung kann man auch auf '>= 0' testen.
Peter D. schrieb:> Viele finden die 2. Variante> lesbarer.
Kommt drauf an, was man damit bezweckt. Wenn man mit Arrays arbeitet
können beide Varianten sinnvoll sein. Generell zähle ich aber lieber
hoch als runter - wohl ein Relikt aus der Kindheit :-) - obwohl ich mal
Astronaut werden wollte :-\
@ Adib (Gast)
>Mir ist es eher egal. Lesbarer finde ich die erste Variante.
Dann nimm sie. Solche Mikrooptimierungen sind zu 99% Unsinn bis
kontraproduktiv.
https://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung#Prinzipien_der_Optimierung
Wenn man WIRKLICH das letzte Prozent an CPU-Leitung rausholen MUSS,
macht man das so oder so besser in Assembler, sinnvollwerweise in einer
separaten Funktion/Datei. Meistens erreicht man durch gescheites Design
deutlich mehr.
Naja hier ist der Compiler genauso unsmart wie der User.
Integer ist der falsche Typ (16 bit) für den Schleifenindex
(Wertebereich 10 bis 0), dadurch wird der Code langsamer und größer als
nötig.
char ist die bessere Wahl. auch hinsichtlich der höheren Zahl von
Speicherstellen für 16 bit Werte.
Berufsrevolutionär schrieb:> Integer ist der falsche Typ (16 bit) für den Schleifenindex> (Wertebereich 10 bis 0), dadurch wird der Code langsamer und größer als> nötig.> char ist die bessere Wahl. auch hinsichtlich der höheren Zahl von> Speicherstellen für 16 bit Werte.
Kann man so allgemein nicht sagen, je nach Architektur kann int auch
schneller sein (viele 32 Bit Architekturen).
Allerdings sollten Schleifenzähler grundsätzlich unsigned sein, wenn
nämlich die Anzahl der durchläufe zur Compilezeit nicht bekannt ist, tut
sich der Compiler mit unsigned xyz nämlich wesentlich leichter bei der
Optimierung.
char ist für Zählvariablen sowieso ungeeignet, da im Standard nicht
definiert ist, ob signed oder unsigned
(http://www.trilithium.com/johan/2005/01/char-types/).
Die richtige Wahl wäre uint_fast8_t: dieser ist definiert als
schnellster unsigned Datentyp mit mindestens 8 Bit Breite.
Will man (aus welchen Gründen auch immer) stdint.h nicht nutzen, wäre
unsigned char Mittel der Wahl.
Ein ordentlicher Compiler versucht die meist vorhandenen
Schleifenhardware zu verwenden. Die kann in der Regel entweder nur
inkrementieren oder dekrementieren. Der Compiler dreht die Zählrichtung
dann, falls nötig.
Die meisten Prozessoren haben Schleifenzähler auf Registergröße.
Kleinere oder größere Zählvariablen erzeugen dann Mehraufwand.
Oft ist ja Aritmetik und Adresseinheit getrennt von der Schleifeneinheit
Dann sollte man den Schleifenzähler nicht für solche Operationen
verwenden.
Sondern eine eigenen lokale Variable.
Das gilt dann auch für den Arrayindex.
Hängt also ganz klar vom Zielsystem ab und wie gut es der Compiler
unterstützt.
Aber generelle gilt schon das man nichts unnötiges erzwingen soll.
Oliver S. schrieb:> Ein richtig ordentlicher Compiler macht bei 10 Schleifendurchläufen loop> unrolling...>> Oliver
den Compiler würde ich in die Tonne werfen.
hab gerade einen Schleife über 5
die aber 8000/s aufgerufen wird
da tut jeder Befehl mehr richtig schmerzen.
Der Compiler kann die 8000/s gar nicht wissen
Volle schrieb:> Oliver S. schrieb:>> Ein richtig ordentlicher Compiler macht bei 10 Schleifendurchläufen loop>> unrolling...>>>> Oliver>> den Compiler würde ich in die Tonne werfen.
Kann ich nicht nachvollziehen.
> hab gerade einen Schleife über 5> die aber 8000/s aufgerufen wird> da tut jeder Befehl mehr richtig schmerzen.
Loop unrolling vergrössert zwar den Code, aber reduziert meist die
Anzahl ausgeführter Befehle. Cache-Effekte aussen vor gelassen ist es
also nicht notwendigerweise hinderlich. Zumal sequentiell ausgeführter
Code bei µCs meist schneller ist als nichtsequentieller Code.
A. K. schrieb:> Volle schrieb:>> Oliver S. schrieb:>>> Ein richtig ordentlicher Compiler macht bei 10 Schleifendurchläufen loop>>> unrolling...>>>>>> Oliver>>>> den Compiler würde ich in die Tonne werfen.>> Kann ich nicht nachvollziehen.>>> hab gerade einen Schleife über 5>> die aber 8000/s aufgerufen wird>> da tut jeder Befehl mehr richtig schmerzen.>> Loop unrolling vergrössert zwar den Code, aber reduziert die Anzahl> ausgeführter Befehle, mindestens wenn die Anzahl Durchläufe bekannt ist.> Cache-Effekte aussen vor gelassen ist es also nicht hinderlich. Zumal> sequentiell ausgeführter Code bei µCs meist schneller ist als> nichtsequentieller Code.
die meisten können Befehle schneller abarbeiten als aus dem Flash laden
weshalb Cache gesetzt ist
mehrstufige Pipelines, parallele Pipelines, Loop Hardware
für all das ist sequenzieller Code schlecht
vor 20 Jahren konnten die Controller mit sequenziellem Code leichter
umgehen. Seither hat sich aber viel geändert
Volle schrieb:> hab gerade einen Schleife über 5> die aber 8000/s aufgerufen wird> da tut jeder Befehl mehr richtig schmerzen.
Und genau deshalb ist Loop Unrolling hier angebracht. Deine Schleife
wird dadurch schneller durchlaufen. Pro Sekunde sogar 8000 Mal
'schneller'.
Volle schrieb:> die meisten können Befehle schneller abarbeiten als aus dem Flash laden> weshalb Cache gesetzt ist
Die meisten µCs ohne Cache können Flash schnell genug sequentiell laden
um nicht wesentlich ausgebremst zu werden. Manche STM32 sind da zwar
hart an der Kante gebaut - nur sind genau die dann nichtsequentiell
mangels Cache langsamer.
Bei Prozessoren mit Cache darf man oft davon ausgehen, dass der Inhalt
von sehr oft ausgeführtem Code (8000/s) im Cache verbleibt. Das ist der
Sinn eines Caches. Cache bremst sequentiellen Code nicht aus, so lange
unrolling nicht so krass gefahren wird, dass der Cache zu klein wird.
> mehrstufige Pipelines, parallele Pipelines, Loop Hardware> für all das ist sequenzieller Code schlecht
Auch bei vielen Highend-CPUs der letzten Jahre führt nichtsequentielle
Ausführung zu einer kleinen Verzögerung im Fetch und zu geringerem
Befehlsdurchsatz, wenn fetch/decode/dispatch/retire den Durchsatz
begrenzt.
> Seither hat sich aber viel geändert
Ich bin auch nicht (nur) von gestern. ;-)
Volle schrieb:> die meisten können Befehle schneller abarbeiten als aus dem Flash laden> weshalb Cache gesetzt ist> mehrstufige Pipelines, parallele Pipelines, Loop Hardware> für all das ist sequenzieller Code schlecht
Kannst Du das konkretisieren?
Auf welche Controller beziehst Du Dich konkret?
> vor 20 Jahren konnten die Controller mit sequenziellem Code leichter> umgehen. Seither hat sich aber viel geändert
Meiner Meinung nach ist das Gegenteil der Fall.
Früher war ein Flash-Zugriff ein echter Flaschenhals. Mittlerweile ist
das Flash bei vielen Controllern sehr breit organisiert (STM32F4xx: 128
Bits). Unter anderem dadurch werden Peformance-Verluste durch
Flash-Waitstaites minimiert.
Viele Grüße, Stefan
vn n. schrieb:> Kann man so allgemein nicht sagen, je nach Architektur kann int auch> schneller sein (viele 32 Bit Architekturen).
Hier ist es ein 8bit Controller AVR, dessen Register 8 bit breit sind.
also ist ein 8bit type angebracht. Integer wird wie im assemblerlisting
zu sehen auf 16bit umgesetzt. Was nicht nur zwei statt einem Befehl bei
der Initialisierung benötigt, sondern auch eines der wenigen 16bit
Registerpaare blockiert, was bei komplexeren Operationen den Compiler
zwingen könnte auf SRAM (Stack) für Variablen zurückzugreifen.
> Die richtige Wahl wäre uint_fast8_t: dieser ist definiert als> schnellster unsigned Datentyp mit mindestens 8 Bit Breite.
Man lernt nie aus -> ein schneller 8bit Typ -> was es nicht alles in
einer Hochsorache gibt. :-0
Soll das ein 8bit Register sein, im unterschied zu einer 8bit
RAM-addresse (auch 8 bit aber Langsam)?
Abgesehen ob fast oder slow-Typen bevorzuge ich auch einen type der die
(register-)breite in bits erkennen lässt, statt char, integer etc. was
je nach Architektur unterschiedlich lang ist. Spannend wird's beim ARM
mit seinem THUMB-Instruction_set/mode, da wechselt die word-breite
innerhalb der Architectur.
Guardians of the memory space schrieb:> Soll das ein 8bit Register sein, im unterschied zu einer 8bit> RAM-addresse (auch 8 bit aber Langsam)?
Nein.
Das ist der schnellste Typ auf der Plattform mit einem Wertebereich von
mindestens 8 Bit. Kann also auch 16 oder 32 oder 64 oder ... Bit
haben.
Was nutzt es dir, mit 8 Bit zu rechnen, wenn die Plattform bei jeder
arithmetischen Operation erst mal eine Sign-Extension machen (und
hinterher wieder "abschneiden") muß, weil sie (z.B.) nur 32-Bit
Operationen kennt?
Stell' dir vor, es gibt tatsächlich auch eine Welt außerhalb von AVR ;)
A. K. schrieb:> Volle schrieb:>> die meisten können Befehle schneller abarbeiten als aus dem Flash laden>> weshalb Cache gesetzt ist>> Die meisten µCs ohne Cache können Flash schnell genug sequentiell laden> um nicht wesentlich ausgebremst zu werden. Manche STM32 sind da zwar> hart an der Kante gebaut - nur sind genau die dann nichtsequentiell> mangels Cache langsamer.
da kenne ich viele Gegenbeispiele ( Freescale, Infineon) ein 160Mhz Risc
ist schneller als das beste Flash
von Multicore will ich gar nicht erst anfangen
> Bei Prozessoren mit Cache darf man oft davon ausgehen, dass der Inhalt> von sehr oft ausgeführtem Code (8000/s) im Cache verbleibt. Das ist der> Sinn eines Caches. Cache bremst sequentiellen Code nicht aus, so lange> unrolling nicht so krass gefahren wird, dass der Cache zu klein wird.
nur wenn du kein Multitasking Betriebssystem hast.
die Caches bei µC sind je recht einfach aufgebaut und nicht mehrfach
assoziativ
>>> mehrstufige Pipelines, parallele Pipelines, Loop Hardware>> für all das ist sequenzieller Code schlecht>> Auch bei vielen Highend-CPUs der letzten Jahre führt nichtsequentielle> Ausführung zu einer kleinen Verzögerung im Fetch und zu geringerem> Befehlsdurchsatz, wenn fetch/decode/dispatch/retire den Durchsatz> begrenzt.>
zero overhead loop haben die meisten modernen CPUs
parallel zu Arithmetik Load/Store ausführen bringt erst in der Schleife
richtig Gewinn
Guardians of the memory space schrieb:> Hier ist es ein 8bit Controller AVR, dessen Register 8 bit breit sind.
Erstens: nein, der TS hat AVR nicht erwähnt.
Zweitens: mein Posting nochmal lesen, ich hab geschrieben, dass man es
Allgemein nicht sagen kann, für konkrete Architekturen allerdings
stimmen kann.
Guardians of the memory space schrieb:> Man lernt nie aus -> ein schneller 8bit Typ -> was es nicht alles in> einer Hochsorache gibt. :-0> Soll das ein 8bit Register sein, im unterschied zu einer 8bit> RAM-addresse (auch 8 bit aber Langsam)?
Nein, schlichtweg ein typedef auf den schnellsten Typ, der mindestens x
Bit breit ist. Auf einem 32-Bit Processor kann dann ein uint_fast8_t
auch mal 32 Bit breit sein (ARM z.B.).
https://stackoverflow.com/a/35055175/7051705
Guardians of the memory space schrieb:> Man lernt nie aus -> ein schneller 8bit Typ -> was es nicht alles in> einer Hochsorache gibt. :-0> Soll das ein 8bit Register sein, im unterschied zu einer 8bit> RAM-addresse (auch 8 bit aber Langsam)?
Mein Gott, das hab ja sogar ich gerafft.
vn n. schrieb:> Die richtige Wahl wäre uint_fast8_t: dieser ist definiert als> schnellster unsigned Datentyp mit *mindestens 8 Bit Breite*
Eine 32bit Cpu wird da eher nicht mit 8bit rumwuschteln und eine 8bit
Cpu sicher nicht mit 16bit....
Volle schrieb:> zero overhead loop haben die meisten modernen CPUs
Nur macht das eine Schleife nicht schneller als unrolled Code ohne Test
und Sprungbefehl.
Wobei auch ein Cortex M7 eine moderne CPU ist, und um diese Kategorie
geht es hier häufiger als um einen Sky Lake.
> parallel zu Arithmetik Load/Store ausführen bringt erst in der Schleife> richtig Gewinn
Kann ich nicht nachvollziehen. Wenn man den Overhead einer Schleife
aussen vor lässt, weil zero overhead loop, dann besteht ansonsten kein
Unterschied zwischen sequentieller und nichtsequentieller Version. Es
sind genau die gleichen Befehle in genau der gleichen präsentieren
Reihenfolge. Weshalb sollte also die nichtsequentielle Version langsamer
sein? Selbst mit microop/trace cache ändert das nichts, so lange der
Code reinpasst.
Adib schrieb:> Hallo Leute,>> es gibt ja so die Regeln beim Optimieren, dass man nicht schlauer sein> sollte als der Compiler und der Compiler schon so optimiert.>> meine Frage:> soll ich jetzt lieber so schreiben:>
1
>#defineLOOPS(10)
2
>for(intloop=0;loop<LOOPS;loop++){
3
>// loop variable nicht verwendet
4
>}
5
>
Obwohl nicht explizit hier erwähnt, vermute ich, dass es sich um C
handelt.
Sollte es C++ sein, so sollte man sich ++loop statt loop++ angewöhnen,
falls man den urspünglichen Zustand nicht braucht.
Auch wenn es für fundamentale Typen keinen Unterschied macht: in
generischem Code kann das post-increment langsamer sein als das
pre-increment, bspw. wenn der Typ von loop nicht int sondern A ist.
Wilhelm M. schrieb:> Obwohl nicht explizit hier erwähnt, vermute ich, dass es sich um C> handelt.
das ist entweder C++ oder C99 +
Vorher durfte man die Laufvariable nicht im for-Statement deklarieren.
Markus F. schrieb:> Wilhelm M. schrieb:>> Obwohl nicht explizit hier erwähnt, vermute ich, dass es sich um C>> handelt.>> das ist entweder C++ oder C99 +
... C90/C99/C11 spielt aber für meine Anmerkung keine Rolle.
Volle schrieb:>> Die meisten µCs ohne Cache können Flash schnell genug sequentiell laden>> um nicht wesentlich ausgebremst zu werden.>> da kenne ich viele Gegenbeispiele ( Freescale, Infineon) ein 160Mhz Risc> ist schneller als das beste Flash
Die haben trotz 160MHz weder Cache noch ausreichend breiten Prefetch?
Oliver S. schrieb:> C99 ist immerhin schon aus dem letzten Jahrtausend, da kann man das> schon mal voraussetzen.>> Oliver
Schon mal bei Microsoft über'n Zaun geguckt? Mein Rat: laß' es ;)
Oliver S. schrieb:> Von AVR ist beim TO niemals die Rede.
Aber bei dem einzigen belastbaren Compiler-Output in diesem Thread und
der TO schließt AVR nicht explizit aus -> die (einzige?)
Optimierungsmöglichkeit in diesem Code liegt in der richtigen type
Auswahl. Die falsche Auswahl des Types kann auch nicht durch
Compilerschalter behoben werden.
Guardians of the memory space schrieb:>> Von AVR ist beim TO niemals die Rede.>> Aber bei dem einzigen belastbaren Compiler-Output in diesem Thread und> der TO schließt AVR nicht explizit aus
Der hat sich seither allerdings auch nicht mehr blicken lassen.
A. K. schrieb:> Volle schrieb:>>> Die meisten µCs ohne Cache können Flash schnell genug sequentiell laden>>> um nicht wesentlich ausgebremst zu werden.>>>> da kenne ich viele Gegenbeispiele ( Freescale, Infineon) ein 160Mhz Risc>> ist schneller als das beste Flash>> Die haben trotz 160MHz weder Cache noch ausreichend breiten Prefetch?
die haben Cache aber das Laden der nächsten Cacheline kann länger
dauern als das ausführen der Aktuellen.
Hängt vom Code, Anfang in der Cacheline, Buss/Crossbar Arbitrierung...
ab
und neueste gehen bis 300MHz
Wenn die CPU da sinnvolle Schleifen dreht entspannt das die Situation
deutlich.
Guardians of the memory space schrieb:> Oliver S. schrieb:>> Von AVR ist beim TO niemals die Rede.>> Aber bei dem einzigen belastbaren Compiler-Output in diesem Thread und> der TO schließt AVR nicht explizit aus -> die (einzige?)> Optimierungsmöglichkeit in diesem Code liegt in der richtigen type> Auswahl. Die falsche Auswahl des Types kann auch nicht durch> Compilerschalter behoben werden.
Da hast Du einerseits recht.
Andererseits sollte man den Typ der Variablen in der for-loop
ausreichend groß, aber eben möglichst klein wählen, d.h. man muss ihn
abhängig von der hier zur Compilezeit bekannten Iterationsanzahl machen
(kann man in C++ leicht mit einer Meta-Funktion). Ist das nicht zur
Compilezeit bekannt, muss man size_t (auf AVR uint16_t) nehmen.
Wilhelm M. schrieb:> Andererseits sollte man den Typ der Variablen in der for-loop> ausreichend groß, aber eben möglichst klein wählen, d.h. man muss ihn> abhängig von der hier zur Compilezeit bekannten Iterationsanzahl machen> (kann man in C++ leicht mit einer Meta-Funktion). Ist das nicht zur> Compilezeit bekannt, muss man size_t (auf AVR uint16_t) nehmen.
Hast ein Beispiel für eine schleife bei der der range des schleifenindex
aka max. Iterationsanzahl nicht zur Compilezeit bekannt ist?!
Volle schrieb:> Wenn die CPU da sinnvolle Schleifen dreht entspannt das die Situation> deutlich.
Eine CPU, die nicht in der Lage ist, sequentiell ungebremst aus dem
CPU-Cache Code zu ziehen, erscheint mir eher exotisch. Üblicherweise
sitzt vorne in der Pipeline ein Prefetch, der genau dafür da ist.
Demgegenüber sind manche Prefetcher recht limitiert, was das Alignment
angeht. Was bei nichtsequentiellem Code spürbar werden kann, bei
sequentiellem Code hingegen keine Rolle spielt.
> Hängt vom Code, Anfang in der Cacheline, Buss/Crossbar Arbitrierung...> ab
Wenn der Zugriff auf den Cache über Bus/Crossbar geht, dann wird es sich
wohl um einen Flash-Cache handeln, nicht um einen CPU-Cache.
L1 CPU-Caches sind üblicherweise Teil des Cores oder direkt dran, nicht
per Bus/Crossbar. Weshalb es dafür auch egal ist, wieviele Cores es
sind.
> und neueste gehen bis 300MHz
300 MHz ohne CPU-Cache ist schon mehr als grenzwertig.
NB: Gibts einen Link zu diesem Unikum?
Guardians of the memory space schrieb:> Aber bei dem einzigen belastbaren Compiler-Output in diesem Thread und> der TO schließt AVR nicht explizit aus
Womit wir dann beim zweiten Punkt wären:
vn n. schrieb:> Zweitens: mein Posting nochmal lesen, ich hab geschrieben, dass man es> Allgemein nicht sagen kann, für konkrete Architekturen allerdings> stimmen kann.vn n. schrieb:> Kann man so allgemein nicht sagen, je nach Architektur kann int auch> schneller sein (viele 32 Bit Architekturen).
Markus F. schrieb:> Oliver S. schrieb:>> C99 ist immerhin schon aus dem letzten Jahrtausend, da kann man das>> schon mal voraussetzen.>>>> Oliver>> Schon mal bei Microsoft über'n Zaun geguckt? Mein Rat: laß' es ;)
Na ja, man muß ja nicht das Negativbeispiel schlechthin bemühen. Wobei
Microsoft ja wenigstens so ehrlich ist, zuzugeben, daß sie ihr "C"-
Compiler überhaupt nicht mehr interessiert.
Oliver
Guardians of the memory space schrieb:> Hast ein Beispiel für eine schleife bei der der range des schleifenindex> aka max. Iterationsanzahl nicht zur Compilezeit bekannt ist?!
Dafür gibt es tausende Beispiele. Sobald einem Programm per Benutzer-
oder Geräteschnittstelle Daten übergeben werden, kann es vorkommen.
1
Gerät: Achtung, ich übertrage jetzt 257 Datensätze...
2
µC: Ok
3
Gerät: Datensatz 1
4
µC: Ok
5
Gerät: Datensatz 2
6
µC: Ok
7
....
8
Gerät: Datensatz 256
9
µC: Urgs..
10
Gerät: Datensatz 257
11
µC: Booting...
Oder nimm ein Kassenprogramm im Laden. Du stehst mit 260 Hosen bei H&M
an der Kasse und diese benutzt einen 8Bit-Schleifenzähler, um die Summe
zu berechnen... Okay, das war wegen "H&M" ein schlechtes Beispiel. ;-)
Guardians of the memory space schrieb:> Wilhelm M. schrieb:>> Andererseits sollte man den Typ der Variablen in der for-loop>> ausreichend groß, aber eben möglichst klein wählen, d.h. man muss ihn>> abhängig von der hier zur Compilezeit bekannten Iterationsanzahl machen>> (kann man in C++ leicht mit einer Meta-Funktion). Ist das nicht zur>> Compilezeit bekannt, muss man size_t (auf AVR uint16_t) nehmen.>> Hast ein Beispiel für eine schleife bei der der range des schleifenindex> aka max. Iterationsanzahl nicht zur Compilezeit bekannt ist?!
Etwa so:
Frank M. schrieb:> Oder nimm ein Kassenprogramm im Laden. Du stehst mit 260 Hosen bei H&M> an der Kasse und diese benutzt einen 8Bit-Schleifenzähler, um die Summe> zu berechnen...
... oder wenn Du nur mit 128 Hosen da stehst und Dir neben den Hosen
auch noch der komplette Kasseninhalt überreicht wird ...
Viele Grüße, Stefan
vn n. schrieb:> Guardians of the memory space schrieb:>> Aber bei dem einzigen belastbaren Compiler-Output in diesem Thread und>> der TO schließt AVR nicht explizit aus>> Womit wir dann beim zweiten Punkt wären:>> vn n. schrieb:>> Zweitens: mein Posting nochmal lesen, ich hab geschrieben, dass man es>> Allgemein nicht sagen kann, für konkrete Architekturen allerdings>> stimmen kann.>> vn n. schrieb:>> Kann man so allgemein nicht sagen, je nach Architektur kann int auch>> schneller sein (viele 32 Bit Architekturen).
Hm, also ich mein nicht allgemein sondern ganz konkret den in diesem
posting
Beitrag "Re: sei nicht schlauer als der Compiler?"
geschilderten Fall, das sich auf das posting bezieht:
Beitrag "Re: sei nicht schlauer als der Compiler?"
welches den m.E. falschen Schluss zieht das der beispielcode in beiden
Fällen optimal für den Compiler wäre. Optimal wird der Code aber erst
wenn der Wertebereich der Variable "passend" ist - da sind wir doch
gleicher Meinung?! - Und um diese Angabe gegenüber dem Compiler machen
zu können, muß der User schlauer sein als der Compiler.
Adib schrieb:> es gibt ja so die Regeln beim Optimieren, dass man nicht schlauer sein> sollte als der Compiler und der Compiler schon so optimiert.
Noch viel wichtiger zu beherzigen ist, dass man nicht dümmer sein
sollte als der Compiler.
Guardians of the memory space schrieb:> vn n. schrieb:>> Guardians of the memory space schrieb:>>> Aber bei dem einzigen belastbaren Compiler-Output in diesem Thread und>>> der TO schließt AVR nicht explizit aus>>>> Womit wir dann beim zweiten Punkt wären:>>>> vn n. schrieb:>>> Zweitens: mein Posting nochmal lesen, ich hab geschrieben, dass man es>>> Allgemein nicht sagen kann, für konkrete Architekturen allerdings>>> stimmen kann.>>>> vn n. schrieb:>>> Kann man so allgemein nicht sagen, je nach Architektur kann int auch>>> schneller sein (viele 32 Bit Architekturen).>> Hm, also ich mein nicht allgemein sondern ganz konkret den in diesem> posting> Beitrag "Re: sei nicht schlauer als der Compiler?"> geschilderten Fall, das sich auf das posting bezieht:> Beitrag "Re: sei nicht schlauer als der Compiler?">> welches den m.E. falschen Schluss zieht das der beispielcode in beiden> Fällen optimal für den Compiler wäre. Optimal wird der Code aber erst> wenn der Wertebereich der Variable "passend" ist - da sind wir doch> gleicher Meinung?! - Und um diese Angabe gegenüber dem Compiler machen> zu können, muß der User schlauer sein als der Compiler.
Nein.
1) In C könnte man das durch den Präprozessor erledigen.
2) In C++:
Wilhelm M. schrieb:>>>> welches den m.E. falschen Schluss zieht das der beispielcode in beiden>> Fällen optimal für den Compiler wäre. Optimal wird der Code aber erst>> wenn der Wertebereich der Variable "passend" ist - da sind wir doch>> gleicher Meinung?! - Und um diese Angabe gegenüber dem Compiler machen>> zu können, muß der User schlauer sein als der Compiler.>> Nein.>> 1) In C könnte man das durch den Präprozessor erledigen.
Ja, dann ist eben der von User genutzte C-Präprozosser schlauer als der
Compiler - läuft auf das selbe hinaus - der Compiler ist nicht schlauer
als sein Input.
Guardians of the memory space schrieb:> Johann L. schrieb:>>> Noch viel wichtiger zu beherzigen ist, dass man nicht dümmer sein>> sollte als der Compiler.>> Wobei es nach Linus Torvalds nix dümmeres als den gcc gibt:
Soll er eben llvm nehmen.
Guardians of the memory space schrieb:> Wilhelm M. schrieb:>>>>>> welches den m.E. falschen Schluss zieht das der beispielcode in beiden>>> Fällen optimal für den Compiler wäre. Optimal wird der Code aber erst>>> wenn der Wertebereich der Variable "passend" ist - da sind wir doch>>> gleicher Meinung?! - Und um diese Angabe gegenüber dem Compiler machen>>> zu können, muß der User schlauer sein als der Compiler.>>>> Nein.>>>> 1) In C könnte man das durch den Präprozessor erledigen.>> Ja, dann ist eben der von User genutzte C-Präprozosser schlauer als der> Compiler - läuft auf das selbe hinaus - der Compiler ist nicht schlauer> als sein Input.
Nein.
Der Compiler macht genau das, was ich der Programmierer ihm sagt. Wenn
da int als Typ steht, darf der Compiler nichts anderes nehmen.
Was hier in dem Beispiel eben fehlt - und das kann der Compiler nicht
alleine - ist eine Beziehung zwischen einer Konstanten auf der einen
Seite und einem Datentyp an anderer Stelle herstellen.
Wilhelm M. schrieb:> Nein.> Der Compiler macht genau das, was ich der Programmierer ihm sagt.
Ganz mein reden, dummer Input bspw 16bit wo 8bit passt -> dummer Output
> Wenn> da int als Typ steht, darf der Compiler nichts anderes nehmen.
Ja, genau ganz mein reden - int ist da suboptimal, das kriegt kein
Optimierer wech.
> Was hier in dem Beispiel eben fehlt - und das kann der Compiler nicht> alleine - ist eine Beziehung zwischen einer Konstanten auf der einen> Seite und einem Datentyp an anderer Stelle herstellen.
Hm, gibt es nicht da doch eine spezifizierte Beziehung resp welche Länge
der Compiler bei Konstanten anzunehmen hat?
"Im allgemeinen ordnet der Compiler einer numerischen, ganzzahligen
Konstanten den kleinstmöglichen Datentyp zu."
aus http://c-buch.sommergut.de/Kapitel3/Konstanten.shtml
Aber damit kann er eben den vom User (grösser als nötig) verlangten
Datentyp nicht verkleinern.
Würde da ein cast bei der Konstanten Deklaration helfen oder zumindest
eine Warning generieren?
> Andererseits sollte man den Typ der Variablen in der for-loop> ausreichend groß, aber eben möglichst klein wählen, d.h. man muss ihn> abhängig von der hier zur Compilezeit bekannten Iterationsanzahl machen> (kann man in C++ leicht mit einer Meta-Funktion). Ist das nicht zur> Compilezeit bekannt, muss man size_t (auf AVR uint16_t) nehmen.
Dafür gibt es doch uint8_fast_t etc.
Guardians of the memory space schrieb:> Hm, gibt es nicht da doch eine spezifizierte Beziehung resp welche Länge> der Compiler bei Konstanten anzunehmen hat?
Literale haben auch einen Typ: int, long, long, unsigned int, ...
> "Im allgemeinen ordnet der Compiler einer numerischen, ganzzahligen> Konstanten den kleinstmöglichen Datentyp zu."> aus http://c-buch.sommergut.de/Kapitel3/Konstanten.shtml
Vielleicht damit der Type der Literale gemeint ...
> Würde da ein cast bei der Konstanten Deklaration helfen oder zumindest> eine Warning generieren?
In der for-loop steht immer noch int ...
Guardians of the memory space schrieb:> Optimal wird der Code aber erst> wenn der Wertebereich der Variable "passend" ist - da sind wir doch> gleicher Meinung?! - Und um diese Angabe gegenüber dem Compiler machen> zu können, muß der User schlauer sein als der Compiler.
Im Gegenteil. Wenn Du, wie Du oben vorgeschlagen hast, tatsächlich mal
die Schleife mit einem 8 Bit breiten Schleifenzähler compilierst, wirst
Du feststellen, daß die meisten Compiler hier schlauer sind als die
(zumindest viele) User.
Ein nur mittelmäßig schlauer Compiler wird nämlich trotzdem mit einem
int rechnen, weil er weiß, daß er für die Addition integer promotion
machen muß.
Und ein noch schlauerer Compiler wird wissen, daß dabei dasselbe
rauskommt, wie wenn er gleich mit int8_t gerechnet hätte und die integer
promotion wieder wegoptimieren.
Wilhelm M. schrieb:> Der Compiler macht genau das, was ich der Programmierer ihm sagt. Wenn> da int als Typ steht, darf der Compiler nichts anderes nehmen.
Wenn der Compiler weiss, dass keine Werte ausserhalb beispielsweise
0..10 vorkommen können, dann kann er die Schleife nach belieben
optimieren, egal mit welcher internen Darstellung. So lange er sich so
verhält, als ob es "int" sei.
Wenn der Compiler sich beispielsweise entschliessen sollte die 10
Iterationen entrollt direkt hintereinader zu schreiben, dann kann die
Variable auch völlig entfallen.
A. K. schrieb:> Wilhelm M. schrieb:>> Der Compiler macht genau das, was ich der Programmierer ihm sagt. Wenn>> da int als Typ steht, darf der Compiler nichts anderes nehmen.>> Wenn der Compiler weiss, dass keine Werte ausserhalb beispielsweise> 0..10 vorkommen können, dann kann er die Schleife nach belieben> optimieren, egal mit welcher internen Grösse. Er muss sich nur so> verhalten, als ob es "int" wäre.
Genau, die as-if-rule.
Das weiß ich also nicht, ob oder ob nicht.
Der avr-gcc machts in diesem Beispiel jedenfalls nicht. Dann kann ich
aber helfen, so wie ich es beschrieben habe.
So etwas macht man dann bespielsweise auch in std::array<>, dort habe
ich einen
geschachtelten Typ, den ich dann nehmen kann. Dann passt es immer(!),
egal ob mit oder ohne Optimierung nach as-if-rule.
Wilhelm M. schrieb:> Der avr-gcc machts in diesem Beispiel jedenfalls nicht.
In der Sprache ist "int" als ein Basistyp der Zielmaschine definiert,
also als Wortbreite dieser Maschine. Allgemeine Optimierungen des
Compilers dürfen also davon ausgehen, dass das ein Typ ist, mit dem die
Zielmaschine perfekt umgehen kann.
A. K. schrieb:> Wilhelm M. schrieb:>> Der avr-gcc machts in diesem Beispiel jedenfalls nicht.>> In der Sprache ist "int" als ein Basistyp der Zielmaschine definiert,> also als Wortbreite dieser Maschine. Allgemeine Optimierungen des> Compilers dürfen also davon ausgehen, dass das ein Typ ist, mit dem die> Zielmaschine perfekt umgehen kann.
Nein, alles was ich weiß ist, dass ein int ein mindestens 16-Bit großer,
vorzeichenbehafteter Typ ist. Und das hat gar nichts mit der
Ziel-Architektur zu tun!!!
Guardians of the memory space schrieb:>> Wenn>> da int als Typ steht, darf der Compiler nichts anderes nehmen.>> Ja, genau ganz mein reden - int ist da suboptimal, das kriegt kein> Optimierer wech.
Nicht ganz richtig, ab den Optimierlevel -O3 wird die ganze Schleife
ausgerollt und es existiert dann keine Zählvariable mehr.
Zu mein oben genannten Beispiel hab ich auch extra eine volatile
Variable "i" in der Schleife gepackt, ab Level -O2 geht der Compiler
nämlich bei und schreibt direkt den Zielwert in "i".
Und ja, wenn der Programmierer nicht weiß was er macht, ist es immer
schlecht. Aber auch das "Problem" mit den 16 bit Zählvariable ist
eigentlich keins. In den oberen Beispiel verliert man ein Zyklus bei dem
AVR, also im schlimmsten Fall 256 Takte, darüber braucht man sowieso ein
int. Wenn es auf diese paar Takte wirklich ankommt, nimmt man halt -O3
oder man guckt sich die betreffende Stelle genauer an.
Was ein schönes Feature wäre, wenn man betreffende Stellen mit -O3 und
andere mit -Os optimieren könnte, also quasi eine For-Schleife in einer
ISR mit -O3 markieren könnte, weil niemand will per Hand eine Schleife
ausrollen, macht die Wartbarkeit ja auch kaputt.
Wilhelm M. schrieb:> Nein, alles was ich weiß ist, dass ein int ein mindestens 16-Bit großer,> vorzeichenbehafteter Typ ist. Und das hat gar nichts mit der> Ziel-Architektur zu tun!!!
Mag sein, dass du das nicht weisst. Ein Compiler wie GCC ist aber
schlauer, denn er weiss dank Analyse des Codes, dass obige Schleife
keine Werte ausserhalb 0..10 annehmen kann. Vorausgesetzt es sind keine
weiteren Referenzen auf "i" vorhanden.
Christian S. schrieb:> Was ein schönes Feature wäre, wenn man betreffende Stellen mit -O3 und> andere mit -Os optimieren könnte, also quasi eine For-Schleife in einer> ISR mit -O3 markieren könnte, weil niemand will per Hand eine Schleife> ausrollen, macht die Wartbarkeit ja auch kaputt.
Stimmt einerseits in diesem Minimalbeispiel.
Andereseits kann einem -O3 die totale Codegröße ziemlich anwachsen
lassen, was man ggf. auch nicht möchte.
Christian S. schrieb:> Was ein schönes Feature wäre, wenn man betreffende Stellen mit -O3 und> andere mit -Os optimieren könnte,
#pragma GCC push_options
#pragma GCC optimize ("O3")
...
#pragma GCC pop_options
A. K. schrieb:> Wilhelm M. schrieb:>> Nein, alles was ich weiß ist, dass ein int ein mindestens 16-Bit großer,>> vorzeichenbehafteter Typ ist. Und das hat gar nichts mit der>> Ziel-Architektur zu tun!!!>> Mag sein, dass du das nicht weisst. Ein Compiler wie GCC ist aber> schlauer, denn er weiss dank Analyse des Codes, dass obige Schleife> keine Werte ausserhalb 0..10 annehmen kann. Vorausgesetzt es sind keine> weiteren Referenzen auf "i" vorhanden.
Nicht jeder, s.o.
Zumindest der avr-gcc macht es nicht. Der macht brav sbiw statt subi.
Und das Problem der nicht angepassten Datentypen beschränkt sich ja
nicht nur auf dieses kleine Beispiel.
A. K. schrieb:> Wilhelm M. schrieb:>> Zumindest der avr-gcc macht es nicht.>> Doch. Er berücksichtig es aber nicht an jeder Stelle, an der du es gerne> hättest.
Es geht doch hier um das Eingangsbeispiel!
Wilhelm M. schrieb:>> Doch. Er berücksichtig es aber nicht an jeder Stelle, an der du es gerne>> hättest.>> Es geht doch hier um das Eingangsbeispiel!
Ja und? GCC führt definitiv eine Wertebereichsanalyse durch. Nur führt
die eben nicht dazu, dass er deshalb für die Variable eine
Registerbreite verwendet, die für AVR optimal ist.
A. K. schrieb:> Wilhelm M. schrieb:>>> Doch. Er berücksichtig es aber nicht an jeder Stelle, an der du es gerne>>> hättest.>>>> Es geht doch hier um das Eingangsbeispiel!>> Ja und? GCC führt definitiv eine Wertebereichsanalyse durch. Nur führt> die eben nicht dazu, dass er deshalb für die Variable eine> Registerbreite verwendet, die für AVR optimal ist.
Du willst es nicht verstehen, oder?
Genau das habe ich doch die ganze Zeit gesagt. Er optimiert nicht zu
8-Bit.
Wilhelm M. schrieb:> Du willst es nicht verstehen, oder?
Ich bezog mich auf diese Aussage:
Wilhelm M. schrieb:> Nein, alles was ich weiß ist, dass ein int ein mindestens 16-Bit großer,> vorzeichenbehafteter Typ ist.
Und das ist schlicht falsch, wenn als "ich" der Compiler gemeint ist. Er
weiss mehr, nutzt dieses Wissen aber nicht so, wie du es gerne hättest.
A. K. schrieb:> Wilhelm M. schrieb:>> Du willst es nicht verstehen, oder?>> Ich bezog mich auf diese Aussage:>> Wilhelm M. schrieb:>> Nein, alles was ich weiß ist, dass ein int ein mindestens 16-Bit großer,>> vorzeichenbehafteter Typ ist.>> Und das ist schlicht falsch, wenn als "ich" der Compiler gemeint ist. Er> weiss mehr, nutzt dieses Wissen aber nicht so, wie du es gerne hättest.
Wieso sollte ich so schizophren sein? Ich bin ich und nicht der Compiler
;D
Wilhelm M. schrieb:> Nein, alles was ich weiß ist, dass ein int ein mindestens 16-Bit großer,> vorzeichenbehafteter Typ ist. Und das hat gar nichts mit der> Ziel-Architektur zu tun!!!
A.K. hat schon recht. Ein int ist schon seit den 70ern so definiert,
dass es die "natürliche Wortbreite" des Ziel-Prozessors abdeckt. Dass
der avr-gcc 16-Bit für einen int nimmt, ist wohl eher praktischen
Gründen geschuldet, denn mit einem 8-Bit int kann man herzlich wenig
anfangen - ganz zu schweigen von zu erwartenden Inkompatiblitäten mit
libc-Funktionen. Kann aber auch sein, dass später die Forderung
"mindestens 16 Bit für ein int" dazugekommen ist.
Übrigens: Es gibt durchaus C-Compiler für AVR, die mit einem 8-Bit int
arbeiten.
Frank M. schrieb:> Wilhelm M. schrieb:>> Nein, alles was ich weiß ist, dass ein int ein mindestens 16-Bit großer,>> vorzeichenbehafteter Typ ist. Und das hat gar nichts mit der>> Ziel-Architektur zu tun!!!>> A.K. hat schon recht. Ein int ist schon seit den 70ern so definiert,> dass es die "natürliche Wortbreite" des Ziel-Prozessors abdeckt. Dass> der avr-gcc 16-Bit für einen int nimmt, ist wohl eher praktischen> Gründen geschuldet, denn mit einem 8-Bit int kann man herzlich wenig> anfangen - ganz zu schweigen von zu erwartenden Inkompatiblitäten mit> libc-Funktionen. Kann aber auch sein, dass später die Forderung> "mindestens 16 Bit für ein int" dazugekommen ist.
Wann was historisch gekommen ist, ist doch hier erst mal egal:
Das gilt:
http://en.cppreference.com/w/c/language/arithmetic_types
Und da steht es so, wie ich es gesagt habe.
>> Übrigens: Es gibt durchaus C-Compiler für AVR, die mit einem 8-Bit int> arbeiten.
Das kann ich beim (avr-)gcc auch ändern, wenn ich will. Nur dann ist es
nicht mehr dem Standard entsprechend.
Frank M. schrieb:> A.K. hat schon recht. Ein int ist schon seit den 70ern so definiert,> dass es die "natürliche Wortbreite" des Ziel-Prozessors abdeckt.
Der C-Standard definiert int mit mindestens 16 bit. Auf einem
32bit-System wird das üblicherweise 32bit haben, garantiert ist es aber
nicht. Man darf sogar auf einem 64bit-System int mit 16 bit
implementieren.
Nur darf man auf einem System mit 8 bit Wortbreite int eben nicht mit 8
bit implementieren.
Frank M. schrieb:> Dass der avr-gcc 16-Bit für einen int nimmt, ist wohl eher praktischen> Gründen geschuldet,
Nö. Sondern weil man -32767..32767 nicht in ein Byte quetschen kann. Das
ist nämlich der Mindeswertebereich "int", aka MIN_INT..MAX_INT in
stdint.h:
http://www.cplusplus.com/reference/climits/
Wilhelm M. schrieb:> Wenn da int als Typ steht, darf der Compiler nichts anderes nehmen.
Doch natürlich darf er. Solange sich die abstrakte Maschine genauso
verhält wie mit int.
Johann L. schrieb:> Wilhelm M. schrieb:>> Wenn da int als Typ steht, darf der Compiler nichts anderes nehmen.>> Doch natürlich darf er. Solange sich die abstrakte Maschine genauso> verhält wie mit int.
Ok, schlecht ausgedrückt. Die as-if Regel hatte ich obrn schon. Aber
auch das ist doch gar nicht der Punkt hier ...
Frank M. schrieb:> Dass der avr-gcc 16-Bit für einen int nimmt, ist wohl eher praktischen> Gründen geschuldet, denn mit einem 8-Bit int kann man herzlich wenig> anfangen - ganz zu schweigen von zu erwartenden Inkompatiblitäten mit> libc-Funktionen. Kann aber auch sein, dass später die Forderung> "mindestens 16 Bit für ein int" dazugekommen ist.
Wie bereits angemerkt schreibt der Standard mindestens 16 Bits vor.
> Übrigens: Es gibt durchaus C-Compiler für AVR, die mit einem 8-Bit int> arbeiten.
Dann sind sie aber noch weniger Standard-konform als avr-gcc (bei dem
nur double zu klein für den Standard ist).
Außerdem kennt avr-gcc -mint8 mit dem ein int dann 8 Bits hat (inclusive
entsprechender Promotion Rules) und ein long 16 Bit. Aber -mint8 ist
keine Multilib-Option, d.h. man sollte tunlichst -nodefaultlibs
-nostdlib verwenden und alle unaufgelösten Symbole selber
implementieren.
Frank M. schrieb:> Wilhelm M. schrieb:>> Nein, alles was ich weiß ist, dass ein int ein mindestens 16-Bit großer,>> vorzeichenbehafteter Typ ist. Und das hat gar nichts mit der>> Ziel-Architektur zu tun!!!>> A.K. hat schon recht. Ein int ist schon seit den 70ern so definiert,> dass es die "natürliche Wortbreite" des Ziel-Prozessors abdeckt.
Wobei sich die Frage auftut, ob die C-Väter jemals geplant haben für 8
bit CPU's C zu verwenden.
Nach meiner Erinnerungen erschienen die C-Compiler für die 8bit
Controller erst reichlich spät. Brian W. Kernighan und Dennis Ritchie
haben damals C ausgeheckt um Unix auf der PDP-11 (16bit-Maschine) zu
programmieren. Somit kann ich mir gut vorstellen das bei "natürliche
Wortbreite" in C nie an 8bit gedacht wurde. 8bit gelten da eben nicht
als Breite für "(data/Instruction-)Wort" sondern als (druckbares)
"Zeichen" im Sinne von ASCII-Code für eine
teletypwriter(tty)/UART-Bediener-Console.
Und die ersten C-Compiler für die kleinen µC taten sich lange schwer
brauchbare Ergebnisse abzuliefern.
Und gelegentlich taucht die Meinung auf, das man eine 8bit
Architektur/Befehlssatz extra darauf auslegen muss um sie effizient in C
programmieren zu können.
Guardians of the memory space schrieb:> Wobei sich die Frage auftut, ob die C-Väter jemals geplant haben für 8> bit CPU's C zu verwenden.
Nein. Es gab nämlich keine als C anfing, jedenfalls keine
Mikroprozessoren. Es gab beispielsweise 12-Bit Minicomputer, aber die
hatten sie auch nicht auf dem Radar.
> Nach meiner Erinnerungen erschienen die C-Compiler für die 8bit> Controller erst reichlich spät.
In den 80ern kamen Schmalspur-Compiler, Small-C, Tiny-C und wie das Zeug
auch immer hiess. Vielleicht gab es auch echtes C, aber C war Anfang der
80er noch nicht die dominante Sprache. Dafür gab es Fortran, PL/I und
Cobol Compiler für CP/M (eine recht eindrucksvolle Leistung).
C Compiler für 8 Bit wurden wirtschaftlich erst interessant, als man C
nicht nur für PCs, sondern auch für Mikrocontroller verwenden wollte.
> Architektur/Befehlssatz extra darauf auslegen muss um sie effizient in C> programmieren zu können.
Wenn ein Compiler von inneren Aufbau her auf register-orientierte
Maschinen ausgerichtet ist, dann tut man sich schwer damit, ihn auf
Akku-Architekturen zu portieren. Es gibt (oder gab) im GCC eine
Zielmaschinenspezifikation für die besseren 68HC1x Akku-Maschinen. Aber
wenn man da reinsieht, dann findet man Pseudoregister im RAM. Auch bei
den Renesas R8c/M16c fand ich so etwas, anscheinend sind deren paar
Register zu wenig.
Ein ähnliches Problem stellte sich mir bei einer Art Vorläufer des GCC,
dem PCC, bei einer Portierung auf Transputer. Auch der PCC setzte eine
gewisse Zahl Register voraus, der Transputer hat aber nur einen Stack.
Das endete mit einem vollständig neu geschriebenen Codegenerator.
Aber auch völlig unabhängig von den Compilern profitiert C von
bestimmten Eigenschaften einer Maschine, die in Assembler auf kleinen
Mikrocontrollern nicht ganz so wichtig sind.
Dazu zählt die Adressierbarkeit von Daten relativ zu Adressregistern
(*). Das ist in C viel wichtiger als in Assembler oder Fortran. Etliche
8-Bit CPUs tun sich damit etwas schwer. Auch AVR-Code sieht man an, das
es zu wenig Pointer-Register gibt. Gute Compiler für 8051 adressieren
deshalb lokale "auto" Daten statisch und treiben gewisse Blüten um
Funktionen ggf. reentrant zu bekommen.
Ein weiterer Punkt ist das Vorzeichen. Manchen 8-Bittern fehlt die
Möglichkeit, direkt auf Basis vorzeichenbehafteter Typen zu springen.
Sei es, weil ihnen das dafür üblicherweise verwendete Overflow-Flag
fehlt, sei es, weil ihnen die Befehle dazu fehlen. Aufgrund der
Promotion-Regeln hat man ein Vorzeichen aber häufiger im Boot, als einem
lieb ist.
Keine gute Idee ist Bank-Switching im RAM. Der erste Entwurf von AVRs
hatte das wohl auch drin, aber ein Compiler-Hersteller kriegte die Krise
und setzte sich durch.
*: Als Zilog die 16-Bit Z8000 Konkurrenz zu 8086 und 68000 rausbrachte,
hatten sie erkennbar eher Fortran als C im Auge. Die kann das zwar,
kommt aber mit *(addresse+indexregister) und *addressregister deutlich
besser klar als mit *(addressregister+offset).
Johann L. schrieb:> Guardians of the memory space schrieb:>> Johann L. schrieb:>>>>> Noch viel wichtiger zu beherzigen ist, dass man nicht dümmer sein>>> sollte als der Compiler.>>>> Wobei es nach Linus Torvalds nix dümmeres als den gcc gibt:>> Soll er eben llvm nehmen.
Oder sich mal überlegen wo er ohne den GCC heute wäre.
Linus schimpft ja gerne. Und, wenn ich mich richtig erinnere, hatte er
auch gute Gründe. Aber es scheint dennoch irgendwie zu gehen. Ansonsten
hätte er schon seinen eigenen Compiler geschrieben. :-) /OT
> Berufsrevolutionär schrieb
[AVR C/ASM-Codebeispiel>
Naja hier ist der Compiler genauso unsmart wie der User.
vn n. schrieb:> Berufsrevolutionär schrieb:>> Integer ist der falsche Typ (16 bit) für den Schleifenindex>> (Wertebereich 10 bis 0), dadurch wird der Code langsamer und größer als>> nötig.>> char ist die bessere Wahl. auch hinsichtlich der höheren Zahl von>> Speicherstellen für 16 bit Werte.>> Kann man so allgemein nicht sagen, je nach Architektur kann int auch> schneller sein (viele 32 Bit Architekturen).> Allerdings sollten Schleifenzähler grundsätzlich unsigned sein, wenn> nämlich die Anzahl der durchläufe zur Compilezeit nicht bekannt ist, tut> sich der Compiler mit unsigned xyz nämlich wesentlich leichter bei der> Optimierung.> char ist für Zählvariablen sowieso ungeeignet, da im Standard nicht> definiert ist, ob signed oder unsigned> (http://www.trilithium.com/johan/2005/01/char-types/).> Die richtige Wahl wäre uint_fast8_t: dieser ist definiert als> schnellster unsigned Datentyp mit mindestens 8 Bit Breite.> Will man (aus welchen Gründen auch immer) stdint.h nicht nutzen, wäre> unsigned char Mittel der Wahl.
Danke für die Ausführungen.
Ich programmierbar halt meist µC maschinennah und finde da die zuweilen
ausufernde Typenvielfalt in C mehr verwirrend als hilfreich. Viele
Typbezeichner sind redundant oder die wortwörtliche Übersetzung hilft
nicht weiter da es die suggerierte Unterscheidung in der jeweiligen
Architektur nicht gibt.
Beispielsweise bringt ein AVR eben Befehle für 8 und 16 bit Daten bit,
Unterscheidung in vorzeichen-typen/Operatoren gibt es garnicht(? oder
ist verzichtbar!).
Also alles im Wortsinne Integer und jeweils normal (8bit) und doppelt
(16bit) breit - wird dann aber in C als "unsigned char" und "integer"
(oder unsigned integer ?) bezeichnet. Und jetzt noch die Unterscheidung
in schnelle und nicht-schnelle Typen?
Ich bin der Meinung, das man den jeweiligen Zielprozessor soweit kennen
sollte, das man die Nach/Vorteile des Benutzung der jeweiligen
Operandenlänge beachtet.
Und dann sollte man eben nur die Bezeichner für Maschinen-Grundtypen
verwenden und nicht irgendwelche aus "Unsigned" "long"
zusammnengefrickelte Synonyme.
Berufsrevolutionär schrieb:> Beispielsweise bringt ein AVR eben Befehle für 8 und 16 bit Daten bit,> Unterscheidung in vorzeichen-typen/Operatoren gibt es garnicht(? oder> ist verzichtbar!).
Doch, es gibt die Unterscheidung zwischen vorzeichenbehafteten und
-losen Zahlen auch beim AVR, siehe MUL, FMUL, MULS, FMULS, MULSU,
FMULSU, LSR¹, ASR¹, NEG, BRLO, BRSH, BRLT und BRGE.
————————————
¹) als Division durch 2 betrachtet
Berufsrevolutionär schrieb:> Beispielsweise bringt ein AVR eben Befehle für 8 und 16 bit Daten bit,> Unterscheidung in vorzeichen-typen/Operatoren gibt es garnicht(? oder> ist verzichtbar!).
Entweder unterscheiden Vergleichsbefehle zwischen vorzeichenlos und
vorzeichenbehaftet (z.B. MIPS), oder Condition Codes enthalten die
nötige Information und die bedingten Sprünge werten sie entsprechend
aus. 8051, 6502, 8080/Z80 und PIC fehlen teils die Flags und allen die
Sprungbedingungen, und sie können deshalb mit vorzeichenbehafteten
Vergleichen deutlich schlechter umgehen.
Vergleichsbefehle lassen sich bei AVRs zudem so kaskadieren, dass das
Ergebnis auch bei Multibyte-Typen passt (dank CPC und der Handhabung des
Z-Flags). Das kenne ich davor nur vom Z8.
Berufsrevolutionär schrieb:> Ich programmierbar halt meist µC maschinennah und finde da die zuweilen> ausufernde Typenvielfalt in C mehr verwirrend als hilfreich.
Eine universelle Programmiersprache kommt kaum darum herum, sowohl für
die verschiedenen Maschinentypen als auch die üblicherweise von
Anwendungen geforderten Typen eine Hochsprachendarstellung zu
ermöglichen. Die Frage ist nur wie. C verwendet dazu die bekannte
Kombination von Schlüsselwörtern, deren Bedeutung in der Implementierung
festgelegt wird. In PL/I schreibt man die Länge schlicht rein: dcl i
binary(7) für eine 8-Bit Variable mit Vorzeichen, vorzeichenlos gab es
m.W. nicht.
> Viele Typbezeichner sind redundant
Wenn man wie in C Bezeichner zur Unterscheidung der Typen verwendet,
dann ist Redundanz schlecht vermeidbar. Es gibt zu viele Variationen von
Maschinen. Wenn eine 32-Bit Maschine keine 16-Bit Datentypen kennt
(32-Bit Transputer), dann sind short=int=long=32. Im LP64 Modell der
64-Bit x86 hingegen besteht keine Redundanz: short=16, int=32, long=64.
> Ich bin der Meinung, das man den jeweiligen Zielprozessor soweit kennen> sollte, das man die Nach/Vorteile des Benutzung der jeweiligen> Operandenlänge beachtet.
Manche meiner Programme und Libraries gehen zurück auf die 80er Jahre
und haben 32bit 68000, 16bit x86, 32bit x86, 64bit x86 und IBM POWER
erlebt. Spätere haben AVR und ARM erlebt. Da lohnt es sich, die
Datentypen nicht auf direkt auf eine einzelne Zielmaschine zu
optimierten, sondern sich über deren Variationen Gedanken zu machen.
Damit man aufgrund verschiedener Interpretationen von z.B. "int" nicht
für jede Portierung die Typen anpassen muss und trotzdem effizient
bleibt.
Ich habe es noch nie erlebt, wenn man Code vom AVR auf den ARM portiert,
daß dann ein uint8_t plötzlich zum Flaschenhals wird.
Die ARM haben eh massig Flash, da fällt ein überzähliges x&=0xFF;
überhaupt nicht auf.
Die xx_fast_xx Typen sind nur was für Pedanten.
Peter D. schrieb:> Ich habe es noch nie erlebt, wenn man Code vom AVR auf den ARM> portiert, daß dann ein uint8_t plötzlich zum Flaschenhals wird.
Das stimmt schon: es ist schwierig, einen STM32 zum Schwitzen zu
bekommen - außer man macht es böswillig oder aus Dummheit.
Hier ein Thread, wo das diskutiert wird:
Beitrag "STM32: Arrays und Performance"
Tatsächlich ist es so, dass die Abarbeitung von Schleifen wesentlich
schneller abläuft, wenn die Laufvariablen 32 Bit breit sind. Als
Array-Indices sind aber demgegenüber uint8_t Variablen etwas flotter.
> Die xx_fast_xx Typen sind nur was für Pedanten.
Das sehe ich anders. Diese Typen fördern die Portabilität von Code für
verschiedenen Zielplattformen, ohne einen Kompromiss bei der Performance
eingehen zu müssen.
Frank M. schrieb:> Das stimmt schon: es ist schwierig, einen STM32 zum Schwitzen zu> bekommen - außer man macht es böswillig oder aus Dummheit.
Wenn man damit sowas baut, kommt der definitiv ins Schwitzen:
http://www.millennium2000.de/chessgenius-pro
(Ist jetzt nicht exakt STM32, aber ein ähnlicher Cortex-M4.)
Peter D. schrieb:> Ich habe es noch nie erlebt, wenn man Code vom AVR auf den ARM portiert,> daß dann ein uint8_t plötzlich zum Flaschenhals wird.> Die ARM haben eh massig Flash, da fällt ein überzähliges x&=0xFF;> überhaupt nicht auf.
Also IMHO sollte der Compiler durch ne Speed-Optimierungsoption dazu
gebracht werden eine uint8_t - Variable automatisch durch eine
schnellere ersetzen können, auch wenn diese breiter als nötig ist. Und
wenns schaltet man eben die Spoeed-Optimierung für diesen Codeteil
wieder aus.
> Die xx_fast_xx Typen sind nur was für Pedanten.
Seit wann gibst die überhaupt in der stdint.h? C11? C99?
Scheint irgendein so ein neumodisches Pflaster für ne alte Krücke zu
sein?
In der stdint.h für AVR finde ich jetzt 26 Typdefinitionen, plus defines
für die Bereichsgrenzen - wer braucht das zum Glücklich sein?
http://www.nongnu.org/avr-libc/user-manual/group__avr__stdint.html
Guardians of the memory space schrieb:> Also IMHO sollte der Compiler durch ne Speed-Optimierungsoption dazu> gebracht werden eine uint8_t - Variable automatisch durch eine> schnellere ersetzen können, auch wenn diese breiter als nötig ist.
Ein Ändern des Typs durch globalen Rundumschlag halte ich für nicht
sinnvoll. Das geht für Arrays (Größe!) oder für uint8_t-Variablen, bei
denen der Überlauf von 255 auf 0 beabsichtigt ist, voll in die Hose.
Außerdem beträfe so eine globale "Optimierungsoption" auch das Interface
zur libc und anderen Bibliotheken.
Nein, das uint_fast8_t muss man schon sehr bewusst einsetzen und man
muss wissen, was das für Konsequenzen haben kann, wie zum Beispiel
fehlenden Überlauf. Ist also nix für Leute, die nicht nachdenken wollen.
Guardians of the memory space schrieb:> Also IMHO sollte der Compiler durch ne Speed-Optimierungsoption dazu> gebracht werden eine uint8_t - Variable automatisch durch eine> schnellere ersetzen können, auch wenn diese breiter als nötig ist.
In realitas werden die fast-typen einfach fest auf passende Int-Typen
getypdefed.
Oliver
Guardians of the memory space schrieb:>> Seit wann gibst die überhaupt in der stdint.h? C11? C99?
C99
> Scheint irgendein so ein neumodisches Pflaster für ne alte Krücke zu> sein?
Nein, an der richtigen Stelle eingesetzt, bekommen damit auch
C-Programme so etwas wie Generizität, weil entsprechend der Plattform
der am besten geeignetste DT gewählt wird (s.o.).
Guardians of the memory space schrieb:> Seit wann gibst die überhaupt in der stdint.h? C11? C99?
stdint.h gibts seit C99.
> Scheint irgendein so ein neumodisches Pflaster für ne alte Krücke zu> sein?
C11 ist neumodisches Pflaster für C99, was ein neumodisches Pflaster für
C89/C90 war, das wiederum ein neumodisches Pflaster für die alte Krücke
K&R-C war.
Guardians of the memory space schrieb:> Also IMHO sollte der Compiler durch ne Speed-Optimierungsoption dazu> gebracht werden eine uint8_t - Variable automatisch durch eine> schnellere ersetzen können
Das darf er und das tut er vielleicht auch, soweit das gleiche Ergebnis
rauskommt. Im Speicher geht das üblicherweise nicht gut, also lässt er
die Finger davon. Im Register wird schon mal eine andere Breite
verwendet oder die Variable ganz wegoptimiert, wie bereits erwähnt.
Ohnehin: Da ARMe keine 8 Bit Register haben muss sich der Compiler
aussuchen, ob er die übrigen 24 oder 56 Bits als Schrott betrachtet,
oder ob er zero- oder sign-extended.
Frank M. schrieb:> Guardians of the memory space schrieb:>> Also IMHO sollte der Compiler durch ne Speed-Optimierungsoption dazu>> gebracht werden eine uint8_t - Variable automatisch durch eine>> schnellere ersetzen können, auch wenn diese breiter als nötig ist.>> Ein Ändern des Typs durch globalen Rundumschlag halte ich für nicht> sinnvoll. Das geht für Arrays (Größe!) oder für uint8_t-Variablen, bei> denen der Überlauf von 255 auf 0 beabsichtigt ist, voll in die Hose.> Außerdem beträfe so eine globale "Optimierungsoption" auch das Interface> zur libc und anderen Bibliotheken.
Man muss ja die Optimierung nicht global benutzen sondern
source-fileweise. Geht bei Makefiles und ähnlichen buildscripts
wunderbar, GUI-User müssen eventuell mehr klicken um punktgenau
optimieren zu können.
das Arrays problematisch sind ist mir bewußt deshalb schrieb ich ja auch
von Variable und meinte damit ein wert in einem einzelnes Register. Und
ob absichtliches Verwenden eines Überlaufes ein sauberer Programmierstil
ist, wäre auch zu hinterfragen.
> das uint_fast8_t muss man schon sehr bewusst einsetzen und man> muss wissen, was das für Konsequenzen haben kann, wie zum Beispiel> fehlenden Überlauf. Ist also nix für Leute, die nicht nachdenken wollen.
Meinst Du damit den TS, der ja jedes Nachdenken dem Compiler überlassen
will?
Hallo Leute,
Danke für all eure Beiträge.
So wie es ausschaut brauche ich mich um diese Micro-Optimierungen keine
Sorge machen. Das macht der Compiler für mich.
Wir sollten halt versuchen, den Code einheitlich zu gestalten, damit
nicht mal hier eine Schleife von 0 hochzählt und dort eine vom Endwert
runter und so was.
Bei Performance-Enpässen bzw. Interrupts schaue ich dann schon zweimal
hin und gebe dem Compiler noch ein paar architekturbedingte Hinweise.
Alles klar und schönes WE.
Adib.
--
Adib schrieb:> Hallo Leute,>> Danke für all eure Beiträge.>> So wie es ausschaut brauche ich mich um diese Micro-Optimierungen keine> Sorge machen. Das macht der Compiler für mich.
Genau. Außerdem spielt es in der Praxis nur selten eine Rolle, ob da ein
Taktzyklus mehr für den Vergleich gebraucht wird oder nicht.
In der Regel ist nur ein sehr kleiner Teil des Code wirklich
zeitkritisch.
Berufsrevolutionär schrieb:> Naja hier ist der Compiler genauso unsmart wie der User.> Integer ist der falsche Typ (16 bit) für den Schleifenindex> (Wertebereich 10 bis 0), dadurch wird der Code langsamer und größer als> nötig.
Genau. Das Problem ist hier eindeutig der User, nicht der Compiler. Das
ist bei modernen Compilern sogar meistens der Fall.
Nur bei dem, womit moderne Compiler immer noch Probleme haben, kann man
als Asm-Programmierer noch ernsthaft gegen sie punkten. Und das sind im
Prinzip "nur" drei bis vier Problemkreise:
1)
Die Dinger optimieren leider nur ziemlich lokal. Insbesondere merklich
bei nebenläufigem (oder quasi-nebenläufigem) Code. ISRs sind das
typische Beispiel. Hier fehlt insbesondere die Abwägung der relativen
Häufigkeit von ISR-Aufrufen und damit die Gewichtung der Anwendbarkeit
von Optimierungen. Das ist ziemlich fatal, bei allem, was viele
Interupts und trotzdem geringen Latenzen benötigt und allem, was
Interrupts mit extrem hohem Durchsatz benötigt.
2)
Die Dinger kennen keine CPU-Flags (auf der Ebene des
Hochsprachen-Quelltextes). Fatal für viele Anwendungen und die
Implementierung nicht-nativer Datentypen.
3)
Die Dinger unterstützen i.d.R. nur eine halbe Handvoll Datentypen
wirklich gut auf jedem Zielsystem, für das es sie gibt. Und selbst hier
wird es oft schon ein wenig eng. Eigentlich sind nur vier Datentypen
wirklich immer sehr gut bis perfekt unterstützt: int in der nativen
Breite des Target und char. Beides jeweils in der signed und unsigned
Variante. Der ganze Rest ist mehr oder weniger Glückssache, abhängig von
Compiler und Target irgendwo zwischen sehr gut brauchbar bis reichlich
lahm oder erst garnicht vorhanden.
4)
Die Dinger kennen bei den neuesten Devices i.d.R. noch längst nicht alle
Tricks, die die Hardware schon beherrscht.
Wenn ich das mit Matt Godbolt's Compiler Exporer übersetze bekomme ich
mit clang 4 und -std=c++11 -O folgendes:
1
test():
2
.LBB0_1:
3
inc byte ptr [rip + i]
4
jmp .LBB0_1
5
i:
Also eine Endlosschleife :)
Mögliche Änderungen: test in main umbenennen, void statt int als Return
in der Signatur, return i ans Ende der Funktion
Siehe auch https://godbolt.org/g/AsaHVU
Jo, und das ist genau so korrekt.
Im Standard steht:
Flowing off the end of a function is equivalent to a return with no
value; this results in undefined behavior in a value-returning function.
Der Compiler hätte auch alternativ das nächstgelegene Atomkraftwerk
sprengen können; sei froh, daß er das nicht gemacht hat.
Wilhelm M. schrieb:> Sollte es C++ sein, so sollte man sich ++loop statt loop++ angewöhnen,> falls man den urspünglichen Zustand nicht braucht.> Auch wenn es für fundamentale Typen keinen Unterschied macht: in> generischem Code kann das post-increment langsamer sein als das> pre-increment
Ein Compiler aus dem Jahre 2017 sollte das erkennen und entsprechend
optimieren können (das eigentliche Thema dieses Threads). Und ob es C++
oder C ist spielt dabei keine Rolle denn dieses eine Konstrukt verhält
sich in beiden Sprachen gleich (und das Compilerbackend ist
wahrscheinlich in weiten Teilen ohnehin das selbe), also gibt es keinen
Grund warum das diesbezüglich irgend einen Unterschied machen sollte.
Bernd K. schrieb:> Wilhelm M. schrieb:>> Sollte es C++ sein, so sollte man sich ++loop statt loop++ angewöhnen,>> falls man den urspünglichen Zustand nicht braucht.>> Auch wenn es für fundamentale Typen keinen Unterschied macht: in>> generischem Code kann das post-increment langsamer sein als das>> pre-increment>> Ein Compiler aus dem Jahre 2017 sollte das erkennen und entsprechend> optimieren können (das eigentliche Thema dieses Threads). Und ob es C++> oder C ist spielt dabei keine Rolle denn dieses eine Konstrukt verhält> sich in beiden Sprachen gleich (und das Compilerbackend ist> wahrscheinlich in weiten Teilen ohnehin das selbe), also gibt es keinen> Grund warum das diesbezüglich irgend einen Unterschied machen sollte.
Ich schrieb oben: ... für fundamentale DT macht es keinen Unterschied
...
Aber in C++ kann es für UDT ein Riesenunterschied sein.
Wilhelm M. schrieb:> Nein.> Der Compiler macht genau das, was ich der Programmierer ihm sagt. Wenn> da int als Typ steht, darf der Compiler nichts anderes nehmen.
Nein.
Wenn die Laufvariable keine Seiteneffekte hat und der Compiler beweisen
kann daß sie einen bestimmten Wertebereich nicht übersteigt darf er
nehmen was er will und was er für angemessen hält. Er darf sie dann
sogar rückwärts zählen lassen wenn das für ihn angenehmer ist.
Bernd K. schrieb:> Wenn die Laufvariable keine Seiteneffekte hat
Sobald eine Variable im Speicher abgelegt wird sind Seiteneffekte nicht
ausschliessbar. Und weiss man ob der später dazugelinkte IRQ-Handler
nicht dreckigerweise Registerwerte zur schnellen Parameterübergabe
nutzt?!
> und der Compiler beweisen> kann
Ich wusste es! - der Compiler ist ein Arschloch das mir ständig was
beweisen will ;-)
> daß sie einen bestimmten Wertebereich nicht übersteigt darf
Ja frag mal die Ariane5 Programmierer wie gut der Compiler im Abschätzen
von Wertebereichen ist. https://de.wikipedia.org/wiki/Ariane_V88> er> nehmen was er will und was er für angemessen hält. Er darf sie dann> sogar rückwärts zählen lassen
Klar und der Programmierer darf sich dann bei Realtime trace die Haare
ausraufen, warum der µC plötzlich subtrahiert statt addiert.
Ordner schrieb:> Sobald eine Variable im Speicher abgelegt wird sind Seiteneffekte nicht> ausschliessbar.
Per Sprachdefinition sind sämtliche Seiteneffekte außerhalb der
Sprachdefinition ausgeschlossen. Damit sind dem Compiler unbekannte
Seiteneffekte vollständig ausschliesbar.
Für die anderen Fälle gibts volatile, das dem Compiler explizit sagt,
das es da für ihn unbekannte Seiteneffekte geben könnte.
Ordner schrieb:> Und weiss man ob der später dazugelinkte IRQ-Handler> nicht dreckigerweise Registerwerte zur schnellen Parameterübergabe> nutzt?!
Der später dazugelinkte IRQ-Handler nutzt deshalb keine Registerwerte
zur Parameterübergabe, weil Parameter bei dem Konzept IRQ-Handler gar
nicht möglich sind. Funktionsaufrufe aus dem Handler heraus folgen dem
ABI, das kennt der Compiler.
Oliver
Oliver S. schrieb:> Ordner schrieb:>> Sobald eine Variable im Speicher abgelegt wird sind Seiteneffekte nicht>> ausschliessbar.>> Per Sprachdefinition sind sämtliche Seiteneffekte außerhalb der> Sprachdefinition ausgeschlossen. Damit sind dem Compiler unbekannte> Seiteneffekte vollständig ausschliesbar.
Ich definiere: "Es gibt keine Fehler" ....
Und wenn der Compiler nicht den gesamten Code zum compilieren bekommt,
sondern auch sourcelose Teile die dazugelinkt werden?
> Für die anderen Fälle gibts volatile, das dem Compiler explizit sagt,> das es da für ihn unbekannte Seiteneffekte geben könnte.
Auch da wo es nicht als volatile markiert ist kann es seiteneffekte
geben. Das kann von dem Controller/OS abhängig sein unter der
compilierte Code läuft (statt im geschützen Register landet die Variable
bei Registerarmen Controllern auf dem anfälligen Stack, Fehler im
Speicherereichsschutz des OS).
Einfach zu definieren - da gibt es nix ist IMHO recht blauäugig.
Je nun, wer auf fehlerhafte Implementierungen angewiesen ist, der hat
Pech. Da ist dann prinzipiell alles möglich, und dagegen kann man dann
auch nichts machen.
Das aber als Normalfall darzustellen, ist dann doch etwas übertrieben.
Oliver
Ordner schrieb:> Und wenn der Compiler nicht den gesamten Code zum compilieren bekommt,> sondern auch sourcelose Teile die dazugelinkt werden?
Ja, das ist natürlich ein Problem, das ...
... irgendwie seit Urzeiten keins ist.
Denn das ist der Normalfall.
Wo ist denn da das Problem?
Oliver
Oliver S. schrieb:> Je nun, wer auf fehlerhafte Implementierungen angewiesen ist, der hat> Pech. Da ist dann prinzipiell alles möglich, und dagegen kann man dann> auch nichts machen.>> Das aber als Normalfall darzustellen, ist dann doch etwas übertrieben.
So ist es aber im real live - Seiteneffekte bestehen und lösen sich
nicht in Luft aus nur weil man diese wegdefiniert. Und so mancher
Programmierer der ein x-- schreibt checkt dann auch den Assemblercoder
nach einem DEC und wirft den Compiler der da meint von sich aus das
umdrehen zu können und INC auf den Müll. Weil im sicherheitsrelevanten
Bereich will man Tools die sich deterministsich verhalten und nicht wie
die Tools es für angemessen halten.
Ordner schrieb:> will man Tools die sich deterministsich verhalten
Ich habe noch keinen Compiler gesehen, der sich nichtdeterministisch
verhalten hätte. Aber schon oft welche, die deterministisch Code
erzeugten, den der Programmierer nicht erwartete.
> und nicht wie die Tools es für angemessen halten.
Dann darfst du Optimierung nicht einschalten.
A. K. schrieb:> Ordner schrieb:>> will man Tools die sich deterministsich verhalten>> Ich habe noch keinen Compiler gesehen, der sich nichtdeterministisch> verhalten hätte. Aber schon oft welche, die deterministisch Code> erzeugten, den der Programmierer nicht erwartete.
Ja weil eben der Sprachstandard da kein Soll setzte, beispielsweise beim
"Register" keyword -> das darf de Compiler beachten muss aber nicht.
Dann ist genau genommen nicht der Compiler undeterministsich sondern der
Sprachstandard.
>> und nicht wie die Tools es für angemessen halten.>> Dann darfst du Optimierung nicht einschalten.
Genau! Hm, fing dieser Thread nicht mal mit der These "überlass den
Compiler das Optimieren" an? Und jetzt die Forderung die Optimierung
auszuschalten?!
Ordner schrieb:> Dann ist genau genommen nicht der Compiler undeterministsich sondern der> Sprachstandard.
Nein. Genaugenommen nutzt du bei Compilern und Systemen, die sich nicht
entsprechend dem Sprachstadard verhalten, halt etwas zu der Sprache
ähnliches, aber nicht die Sprache. In dem Fall halt "Similar C", "Fake
C", oder sowas, aber kein C.
>> Dann darfst du Optimierung nicht einschalten.>> Genau! Hm, fing dieser Thread nicht mal mit der These "überlass den> Compiler das Optimieren" an? Und jetzt die Forderung die Optimierung> auszuschalten?!
Siehe oben. Der Compiler darf jede Optimierung und Änderungen
durchführen, die nach Definition des Sprachstandards zu den vom
Programmierer vorgegeben Ausdrücken äquivalent sind.
Führt das zu unerwünschten Efekten, weil halt in "Similar C"
programmiert wird, und nicht in C, dann muß man sich nicht über dn
C-Standard beschweren.
Oliver
Oliver S. schrieb:> Siehe oben. Der Compiler darf jede Optimierung und Änderungen> durchführen, die nach Definition des Sprachstandards zu den vom> Programmierer vorgegeben Ausdrücken äquivalent sind.>> Führt das zu unerwünschten Efekten, weil halt in "Similar C"> programmiert wird, und nicht in C, dann muß man sich nicht über dn> C-Standard beschweren.
Nein, ich meine nich Similar C sondern Standard-C das es an einigen
Stellen dem Compiler freistellt keywords zu ignorieren. Beispielweise
"inline" wie dort: Beitrag "Re: Assembler (AVR) Freaks bitte: der schnellste Weg, einen ganzzahligen Wert zu skalieren?"
dargestellt.
Ja der Compiler darf die Optimierung durchführen, muß aber nicht. Und
das führt eben zu unterschiedlichen Code, was man nicht immer tolerieren
kann - auch wenn der Code im Prinzip das selbe macht.
Ordner schrieb:>> Dann darfst du Optimierung nicht einschalten.>> Genau! Hm, fing dieser Thread nicht mal mit der These "überlass den> Compiler das Optimieren" an? Und jetzt die Forderung die Optimierung> auszuschalten?!
Du hattest vorhin selbst gefordert, dass der Compiler in
sicherheitkritischen Bereichen den Code Schritt für Schritt genau so
erzeugen solle, wie es der Quellcode suggeriert. Das ist aber praktisch
nur eine andere Formulierung für den Verzicht auf Optimierung.
Lässt man deine Forderung weg, wird Optimierung wieder zulässig. Man
muss aber den Sprachstandard kennen und darf nicht erwarten, dass der
Compiler eher der Intuition des Programmierers als dem Standard folgen
wird.
Letztlich ist es die Frage, was man von der Programmiersprache erwartet.
Ist es ein besserer Makroassembler, nur lesbarer und portabler. Oder ist
es eine abstrahierende Hochsprache. Abstrahierend bedeutet, dass du
nicht mehr genau weisst, was genau dabei als Befehlsablauf herauskommt.
Ordner schrieb:> Ja weil eben der Sprachstandard da kein Soll setzte, beispielsweise beim> "Register" keyword -> das darf de Compiler beachten muss aber nicht.
Das ist ein Relikt aus jenen Zeiten, in die Compiler ungefähr so
arbeiteten, wie ich grad eben als "besserer Makroassembler" beschrieb.
Sie also ohne nennenswerte Optimierungen über die Grenzen von Statements
hinaus übersetzten (*).
Da in dieser Generation folglich auch keine Registeroptimierung
stattfand, wurde die auf den Programmierer verlagert. Der Programmierer
definierte, welche Variablen wichtiger sind als andere. Dass der
Compiler das nicht zwingend beachten muss ergibt sich aus der
unterschiedlichen dafür einsetzbaren Anzahl Register verschiedener
Zielmaschinen.
Bei der PDP-11 waren das 3, bei der VAX wesentlich mehr, und bei einer
Akku- oder Stack-Maschine hat man überhaupt keine. Es ergibt wenig Sinn,
eine portabel gemeinte Sprache so definieren, dass alle Programme für
jede Zielmaschine querbeet umgeschrieben werden müssen.
Dieses Keyword ist heute völlig bedeutungslos geworden und nur noch
zwecks Kompatibilität mit altem Code noch vorhanden.
*: Weshalb es damals auch noch kein "volatile" gab. Man brauchte es
schlichtweg nicht.
Ordner schrieb:> Ja der Compiler darf die Optimierung durchführen, muß aber nicht. Und> das führt eben zu unterschiedlichen Code, was man nicht immer tolerieren> kann - auch wenn der Code im Prinzip das selbe macht.
Kein Compiler wird über die gesamte Lebensdauer eines Programms einen
Quellcode in garantiert immer die gleiche Codesequenz übersetzen, wenn
es zulässig ist, den Compiler upzudaten. Schon der erste Bugfix kann die
Codesequenz ändern, und sei es nur indem er den falschen Code durch
richtigen ersetzt.
Was du suchst hat reinweg nichts mit der offiziell definierten Sprache C
zu tun. Nicht einmal mit einer beliebigen anderen Hochsprache. Wenn du
die exakte Sequenz über die gesamte aktiv gepflegte Lebensdauer deines
Programms reproduzierbar erhalten willst, gibt es nur eine zulässige
Sprache: Assembler.
A. K. schrieb:> Wenn du> die exakte Sequenz über die gesamte aktiv gepflegte Lebensdauer deines> Programms reproduzierbar erhalten willst, gibt es nur eine zulässige> Sprache: Assembler.
Noch nicht mal das.
Dann mußt Du auch archaische Prozessoren einsetzen.
Mit Register Renaming in superskalaren Prozessoren kannst Du nicht mehr
davon ausgehen, daß dein Programm so ausgeführt wird, wie Du's
geschrieben hast. Selbst wenn Du in Assembler programmierst. Und das
kann beim nächsten Microcode-Upgrade schon wieder ganz anders aussehen.
Ordner schrieb:> So ist es aber im real live - Seiteneffekte bestehen und lösen sich> nicht in Luft aus nur weil man diese wegdefiniert.
Umgekehrt wird ein Schuh draus: "Seiteneffekte" (furchtbare Übersetzung
von "side effects") entstehen nicht einfach so aus dem Nichts, so dass
Code grundsätzlich davon ausgehen müsste, dass sie jederzeit und überall
unerwartet auftreten können. Wird nicht explizit definiert, dass es
welche gibt, dann gibt es sie eben nicht, ganz ohne dass man sie dazu
erst "wegdefinieren" müsste.
> Und so mancher Programmierer der ein x-- schreibt checkt dann auch den> Assemblercoder nach einem DEC und wirft den Compiler der da meint von sich> aus das umdrehen zu können und INC auf den Müll.
Wenn er nicht weiß, wie C funktioniert, ist das sein Problem und nicht
die Schuld von C. Wer je schon mal von einem stark optimierenden
Compiler erzeugten Code im Debugger im single-step-Modus durchlafen hat,
hat eine gute Vorstellung davon, was der Compiler da alles umbaut.
Ordner schrieb:> Klar und der Programmierer darf sich dann bei Realtime trace die Haare> ausraufen, warum der µC plötzlich subtrahiert statt addiert.
Nein, normalerweise weiß man dass man beim Debugging von optimiertem
Code oftmals rosa Elefanten sehen wird wo man grüne Nilpferde vermutet
hätte. Aber es zählt nur was hinten rauskommt. Wenn ich einen Bug in
meinem Algorithmus selbst vermute und nachvollziehen will wie das was
ich da hingeschrieben habe ablaufen würde schalt ich die Optimierungen
vorübergehend aus.
Ordner schrieb:> Auch da wo es nicht als volatile markiert ist kann es seiteneffekte> geben.
Dann hat der Programmierer Scheiße gebaut weil er vergessen hat
hinzuschreiben was sein Code eigentlich bewirken soll.
> So ist es aber im real live - Seiteneffekte bestehen und lösen sich> nicht in Luft aus
Real-Live? Folgendermaßen funktioniert es reibungslos im Real-Life:
Sachen die nur rumstehen aber offensichtlich keinem erkennbaren Zweck
dienen und niemandem zu gehören scheinen fliegen kurzerhand raus denn
der Compiler hat weder Zeit noch Platz zu verschwenden für toten oder
unnötig umständlichen Code.
Wenn sie nicht rausfliegen sollen dann soll derjenige der das so haben
will gefälligst einen entsprechenden Zettel drankleben, der Compiler
kann ja sonst nicht riechen daß das unbenutzte Zeug noch irgendeinem
Zweck dient.