Hallo,
ich beschägtige mich zurzeit mit einem Programm in dem es sehr auf
ablaufgeschwindigkeit ankommt.
Ich muss dabei eine if abfrage machen in der ich einfach zwischen True
oder False unterscheide.
Nun könnte ich bei der if ja einfach schreiben "if(Variable_A)" was mich
daran stört ist dass beide Fälle (True/False) für mich eigentlich
Leitung1 und Leitung2 bedeutet. Deswegen würde ich es der Lesbarkeit
halber lieber so schreiben "if(Variable_A == Leitung1)".
Ich frage mich, ob es unterschiede in der Auswertung gibt.
Weiß das jemand? Gibt es unterscheidungen zwischen einer Boolschen
Abfrage und einem gleichwertigem Vergleich?
Gruß Skittler
Was die Optimierung angeht: Jeder brauchbare Compiler wird bei if(a !=
0) und if(a) den gleichen Code erzeugen. Bei if(a == 1) allerdings
nicht, denn das hat eine andere Bedeutung.
Allerdings sei dringend empfohlen, true/false nur dort zu verwenden, wo
das auch Sinn ergibt. Was hier nicht der Fall ist. Hier passt eine
Enumeration besser. Wenn das auf's allerletzte Bit optimiert werden
muss: if(a != 0) kann je nach Prozessor schneller sein als if(a == 1).
Wenn die Variable global ist und es sich um AVRs handelt: Für einzelne
Bits haben diverse AVRs 3 freie Bytes im I/O-Bereich, die sehr effizient
bitweise manipuliert und abgefragt werden können (GPIORx).
Das hängt vom Compiler und von dem Optimierungsgrad ab.
Am besten ist es wenn du dir einfach das Listfile ansiehst, das
generiert wird. Beim x86 ändert sich nur ein Befehl.
Wenn du sowas machst wie
1
if(i==1)
2
c=1;
3
else
4
c=2;
Dann wird auf 1 geprüft. Wenn du schreibst
1
if(i)
2
c=1;
3
else
4
c=2;
Wird auf 0 geprüft. Aber nicht jede Architektur kennt die gleichen
befehle. Manche können auf 0 Prüfen, aber nicht auf !=0.
vergleichbar sein.
Etwas anderes ist es, wenn Du mit bestimmten Werten vergleichst.
Etwa
1
if(a==1)
Der Unterschied besteht darin, das viele Prozessoren, Befehle haben, die
nach Rechen-Operationen (unter Umständen schon nach Ladeoperationen) das
sogenannte Zero-Flag setzen.
Dann ist ein nachfolgendes
1
if(a)
oder auch
[c]
if(a == 0)
natürlich recht schnell anhand des Flags zu entscheiden.
Wenn aber mit anderen Werten als 0 verglichen wird, dann muessen die
Operanden in der Regel tatsächlich nochmal durch eine Operation wie
subtract, oder compare. Das kann je nach Breite des Datentyps auch
mehrere Befehle beinhalten.
Die Breite spielt natürlich auch bei den == 0 und != 0 Vergleichen eine
Rolle. Viele Compiler sind aber so schlau, das Z-Flag über mehrere Bytes
mitzuschleppen.
Im Einzelfall hilft sicherlich ein Blick in das Assemblerlisting.
Skittler wrote:
> Hallo,>> ich beschägtige mich zurzeit mit einem Programm in dem es sehr auf> ablaufgeschwindigkeit ankommt.>
Punkt 1
Ich bezweifle ganz stark, dass dir diese 'Optimierung' den
entscheidenenden Geschwindigkeitsschub bringt, der den Unterschied
zwischen läuft und läuft nicht ausmacht. Auf der anderen Seite
benötigt man manchmal wirklich nur die letzten paar Zehntel-Prozent
an Laufzeit um in den grünen Bereich zu kommen und hat bereits alle
algorithmischen Möglichkeiten ausgereizt.
Punkt 2
Beide Konstrukte sind grundsätzlich mal nicht gleichwertig.
Bei
if( Variable_A )
kann Variable_A irgendeinen Wert haben, solange er nicht 0 ist.
Bei
if( Variable_A == Leitung1 )
wird hingegen gefordert, dass Variable_A exakt den Wert 1 haben muss.
Bei dir macht das offenbar keinen Unterschied, da Variable_A per
Definition nur die Werte 0 oder 1 haben kann (das stimmt doch,
oder?). Aber wir alle wissen, dass sich Definition im Laufe eines
Programmlebens auch mal ändern können. Und dann fallen einem genau
diese kleinen Unterschiede auf den Kopf
Was du machen kannst
1
#define IS_LEITUNG_1(x) (x)
2
3
4
....
5
6
7
if(IS_LEITUNG_1(Variable_A))
Damit hast du an der Abfrage immer noch in lesbarer Form dokumentiert,
was die Absicht des Vergleiches ist und kriegst trotzdem die
möglicherweise etwas verminderte Laufzeit des Vergleiches auf nicht 0.
Und falls sich mal an den Basisannahmen mal ws ändern sollte, hast du
eine zentrale Stelle (das Makro) an der du das berücksichtigen kannst.
Nichts gegen vorrauschauende Entwicklungsweisen, aber allgemein sollte
man nur optimieren, was zu langsam ist - ansonsten sollte Lesbarkeit
immer Vorrang haben.
Maxi wrote:
> Die Breite spielt natürlich auch bei den == 0 und != 0 Vergleichen eine> Rolle. Viele Compiler sind aber so schlau, das Z-Flag über mehrere Bytes> mitzuschleppen.
Das hängt eher von der Architektur (Befehlssatz) als vom Compiler ab.
Peter Stegemann wrote:
> Nichts gegen vorrauschauende Entwicklungsweisen, aber allgemein sollte> man nur optimieren, was zu langsam ist - ansonsten sollte Lesbarkeit> immer Vorrang haben.
"Never start optimizing before you have profiled it."
Jörg Wunsch wrote:
> Peter Stegemann wrote:>> Nichts gegen vorrauschauende Entwicklungsweisen, aber allgemein sollte>> man nur optimieren, was zu langsam ist - ansonsten sollte Lesbarkeit>> immer Vorrang haben.>> "Never start optimizing before you have profiled it."
Premature optimization is the root of all evil
Donald E. Knuth
@ A. K.
>Das hängt eher von der Architektur (Befehlssatz) als vom Compiler ab.
Hä? Wenn ich den C-Datentyp in einem_ Programm auf _einer Maschine
variiere, dann hängt es sehr wohl vom Compiler ab, ob oder ob er nicht
das Z-Bit mitschleppt.
Keine Ahnung was Du da gelesen hast.
Vielleicht reden wir aneinander vorbei.
Es gibt Architekturen, bei denen Vergleichsbefehle so kaskadiert werden
können, dass man abschliessend ein Z-Flag für den Gesamtwert erhält. AVR
ist ein Beispiel dafür. Andere Architekturen hingegen liefern im Z-Flag
(soweit es sowas überhaupt gibt) stets nur die Information des zuletzt
verglichenen oder subtrahierten Datenwortes, was bei Vergleichen von
Datentypen jenseits der Wortbreite der Maschine zu längerem Code führt.
Und der Compiler kann nicht besser sein als die Zielmaschine hergibt.
Wobei ich hoffnungsvoll davon ausgehe, dass jeder Compiler für AVR einen
16- oder 32-Bit-Compare entsprechend umsetzt.
solche sachen wie
if(blala) sind meistens wenig durchdacht das erinnert mich an meine
studentenzeit wo es immer hiese
if(name)
{assert blala
strln blala
strcpy blala
}
else blala
if(name) war immer vorhanden selbst wenn in name nur Müll drinnen stand
als wenn abfragen dann richrige
@ A. K.
>Vielleicht reden wir aneinander vorbei.
Das halte ich für möglich.
Ich liefere hier doch keine umfassenden und widerspruchsfrei
formulierten Analysen nur damit Du einen Streit um des Kaisers Bart
haben kannst.
Bestätigung für Deine Ansichten findest Du in jedem guten Bucher über
Compilerbau.
Nichts füt ungut.
Maxi schrob:
> Im übrigen habe ich nicht von ArchitekturEN sonder von EINER Architektur> gesprochen. Ist das jetzt klar? Mann, mann, mann.
Von welcher Architektur denn?
Du schiebst nur was von "viele Architekturen" oben, und der OP bezog
sich auch nicht speziell auf eine Architektur.
Maxi schrob:
> Der Unterschied besteht darin, das viele Prozessoren, Befehle haben, die> nach Rechen-Operationen (unter Umständen schon nach Ladeoperationen) das> sogenannte Zero-Flag setzen.
A.K. machte einige Anmerkungen speziell zu AVR, die allesamt korrekt
sind.
Wenn es offenbar Missverständnisse gibt, solle man die ausräumen anststt
rumzupöbeln, das ist meine bescheidene Meinung.
Missverständnisse sind sich nicht schlimmes. Es ist wesentlich
erbaulicher, sie zu erkennen, auszuräumen und der Duskussion darauf
ausbauend mehr Tiefe zu verleihen, als sich darüber zu erhitzen.
A. K. wrote:
> Es gibt Architekturen, bei denen Vergleichsbefehle so kaskadiert werden> können, dass man abschliessend ein Z-Flag für den Gesamtwert erhält. AVR> ist ein Beispiel dafür. [...]> Und der Compiler kann nicht besser sein als die Zielmaschine hergibt.> Wobei ich hoffnungsvoll davon ausgehe, dass jeder Compiler für AVR einen> 16- oder 32-Bit-Compare entsprechend umsetzt.
Defür kann und derf er schlechter sein ;-)
Speziell für avr-gcc hatten wir das Thema ja schon des öfteren. Der
AVR-Teil ist geschlagen mit dem, was die maschinenunabhängigen
Optimierer, dir für i386 etc getunet sind, abliefern.
Nehmen wird mal folgendes kleines Testprogramm:
1
intfoo(inty,intz)
2
{
3
if(y)
4
returnz;
5
return0;
6
}
Dafür liefert gcc die optimale Ausgabe
1
foo:
2
or r24,r25
3
brne .L4
4
ldi r22,lo8(0)
5
ldi r23,hi8(0)
6
.L4:
7
movw r24,r22
8
ret
Das Testprogramm verändern wir minimal zu einem Vorzeichen-Vergleich,
bei
dem einfach auf Bit 15 von y getestet werden kann/könnte:
1
intfoo(inty,intz)
2
{
3
if(y<0)
4
returnz;
5
return0;
6
}
Dann lässt und gcc 4.f folgenden Code bewundern:
-- Er nimmt das High-Byte von y und macht einen 8-Bit Wert daraus
-- Aus diesem 8-Bit Wert mach er einen einen signed 16-Bit Wert
-- Er schiebt diesen Wert um 15 nach rechts (Ergebnis ist 0 oder -1)
-- Er maskiert z mit diesem Wert, das Ergebnis ist 0 oder z.
Klasse! Es werden keine Sprünge mehr gebraucht! Optimal für einen
Boliden wie i386 (Das SBRC ist innerhalb der Komplementbildung, es ist
kein Sprung für den Compiler). Für AVR ist es ne Strafe:
1
foo:
2
# MSB extrahieren
3
mov r18,r25
4
# auf 16 Bit signed expandieren
5
clr r19
6
sbrc r18,7
7
com r19
8
# Um 15 nach rechts schieben
9
lsl r19
10
sbc r18,r18
11
mov r19,r18
12
# z mit der Maske verUNDen
13
and r18,r22
14
and r19,r23
15
# return
16
movw r24,r18
17
ret
Die gleichen Kapriolen macht er wenn man auf Bit 15 von y testet und
auch, wenn man das Testergebnis in eine Zwischenvariable speichert:
1
intfoo(inty,intz)
2
{
3
charx=y<0;
4
if(x)
5
returnz;
6
return0;
7
}
Es ist also nicht offensichtlich und für den Programmierer nicht
nachvollziehbar, wie der Compiler auf diesen Code kommt.
Umformulierungen haben kaum eine Wirkung oder garkeine, weil die
algebraisch das gleiche bedeuten.
Tipps kann man also praktisch keine geben, ausser den, möglichst
verständlich zu programmieren und zu hoffen, daß eine neuere
Compiler-Version besseren Code macht. Und nicht noch mehr Kapriolen.
Wenn man wirklich in einer konkreten Anwendung Zeitprobleme hat, die bei
einem bestimmten Compiler auf solche überintelligenten Transformationen
zurückzuführen sind, ist guter Rat teuer.
Allgemeine Tipps gibt es wie gesagt nicht. Der ergriffenen Optimierungen
sind abhängig vom Compiler, seiner Version, seiner Beschalterung, der
Zielarchitektur, dem Quellcode und der Kontext, in dem er steht und von
der Menge an Information, die der Compiler hat.
Konkret für das Beispiel oben fällt mir nix ein ausser Hack, den ich
hier nicht posten möchte ;-) Der Code wird damit zu
1
foo:
2
ldi r18,lo8(0)
3
ldi r19,hi8(0)
4
sbrs r25,7
5
movw r22,r18
6
.L4:
7
movw r24,r22
8
ret
Als Alternative andere Stellen suchen, wo sich was rausholen lässt. Aber
da legt einen der Compiler womöglich auch aufs Kreuz...
Klaus Wachtler wrote:
> Lesbarkeit und Geschwindigkeit bekommt man vielleicht schöner zusammen> mit einem switch:
Vorsicht, ein switch ist oft langsamer. Ich hatte auch immer sowas im
Ohr, ein switch sei bei integern viel besser - hat sich in der Praxis
aber als falsch rausgestellt. Natuerlich kann sich das von Platform zu
Platform auch wieder unterscheiden...
Bei Pic macht diese unterschiedliche IF Abfragen sehrwohl einen
Unterschied in der Codegröße als auch in der Geschwindigkeit, welche
jedoch vernachlässigbar ist. Weiters darf man nicht auf ==1 vergleichen,
da dies dann eine Konstante ist, sondern auf TRUE, wobei TRUE und FALSE
so definiert werden:
#define FALSE 0
#define TRUE !FALSE
oder
#define FALSE (1==0)
#define TRUE (1==1)
je nach Architektur und C Compiler bzw Optimierer.
Merkwürdige Compiler. Diese Ausdrücke sind nach der eigentlich üblichen
Auswertung konstanter Rechnungen sowohl dem Typ als auch dem Wert nach
gleich. !0 ist dann genauso eine Konstante wie 1.
Grad mal mit C18 ausprobiert. Bei 1==1 kommt bei Standard-C (also mit
integer promotions) tatsächlich etwas anderes raus - aber schlechter.
Die beiden anderen sind gleich.
Chris wrote:
> #define FALSE 0> #define TRUE !FALSE>> oder>> #define FALSE (1==0)> #define TRUE (1==1)
Unfug. Lies dir bitte die FAQ der Gruppe comp.lang.c durch, dort
wird ausgiebig diskutiert, warum es Unfug ist.
Wenn du einen C99-fähigen Compiler hast, dann nimm
#include <stdbool.h>
Das liefert dir den Typ bool mit den Werten true und false. Ansonsten
nimm
#define FALSE 0
#define TRUE 1
True als !FALSE definiert ist ein Boolean,
hingengen TRUE als 1 definiert ist ein konstanter Integer oder Char,
was auch immer.
Bei micros hat man nicht immer einen C99 Compiler, und auch wenn man den
hat, heißt nicht, daß BOOL_ auch implementiert ist.
Ein Beispiel aus einer Faq von einem C99 Group
Why is a bool type an advantage? You can get a variety of opinions on
whether this is a step forward. In C, common usage to mimic this type
would be as follows:
typedef int Bool;
#define FALSE 0
#define TRUE 1
One problem with such an approach is that it's not at all type-safe. For
example, a programmer could say:
Bool b;
b = 37;
Hingegen, angenommen man hat keinen C99, dann wenn man
#define TRUE !FALSE
definiert hat, funktioniert das, als wenn es einen Bool-Type wäre,
da dann bei if(b==TRUE) das gleiche ist wie bei if(b), aber bei
if (b==1) oder auch (b==TRUE) wenn TRUE mit 1 definiert wird, das
Ergebnis
schlichtwegs falsch ist.
Chris wrote:
> Hingegen, angenommen man hat keinen C99, dann wenn man> #define TRUE !FALSE> definiert hat, funktioniert das, als wenn es einen Bool-Type wäre,> da dann bei if(b==TRUE) das gleiche ist wie bei if(b), aber bei> if (b==1) oder auch (b==TRUE) wenn TRUE mit 1 definiert wird, das> Ergebnis schlichtwegs falsch ist.
Wenn man keinen C99 hat, dann C89 - wenn wir bei Standards bleiben und
individuelle Erweiterungen aussen vor lassen. C89 kennt kein "bool",
daher ist !0 in Wert und Typ identisch mit 1==1 und mit 1.
Da C99 existierende Programme nicht allzu sehr über den Haufen wirft,
kann sich ohne explizite Verwendung des Datentyps "bool" daran auch
nicht viel ändern. Erst wenn Daten explizit als "bool" spezifiziert
werden kann der Compiler anderen Code erzeugen, weil er andere Werte als
0 und 1 ausschliessen darf.
Unterschiedliches Optimierungsverhalten von C89 und C99 setzt also
voraus, dass man bei C89 irgendwo ein "typedef int bool;" oder sowas in
der Art stehen hat. Unterschiedliches Ergebnis kriegt man aber auch dann
nur, wenn was anderes als 0 oder 1 auftreten kann. Ist dann aber sowieso
im undefinierten Bereich.
Peter Stegemann wrote:
> Klaus Wachtler wrote:>> Lesbarkeit und Geschwindigkeit bekommt man vielleicht schöner zusammen>> mit einem switch:>> Vorsicht, ein switch ist oft langsamer. Ich hatte auch immer sowas im> Ohr, ein switch sei bei integern viel besser - hat sich in der Praxis> aber als falsch rausgestellt.
Jo, das hängt natürlich vom Compiler ab, wie er switch umsetzt.
Prinzipiell gibt es 3 Möglichkeiten:
Sprungtabelle
Der switch-Wert wird als Index in eine Sprungtabelle verwendet, in der
die Adressen der einzelnen case-Labels stehen. Diese Strategie ist bei
dicht besetzten case-Werten angebracht und wenn es eine bestimmte
Mindestanzahl an Labels gibt.
Die Zeit zu einem Label ist konstant.
Spaghetti
Entspricht einem if {} else if {} ... else{} Spaghetti.
Empfehlenswert bei recht wenigen Labels oder dünn besetzten Indices.
Zeit ist linear.
Binäre Suche
Es wird Code erzeugt, der in if-else-Abfragen die Anzahl verbleibender
Labels halbiert. Es werden also keine == oder != Vergleiche erzeugt wie
bei Spaghetti, sondern zB < oder >= Vergleiche.
Die Vergleiche separieren Intervalle, die nur 1 Index enthalten, auf den
dann explizit geprüft wird. Bei dicht besetzten Indices vereinfacht sich
das etwas, weil keine default-Werte zwischen den Indices sind.
Zeit ist logarithmisch.
Und die Theorie sagt, dass der Compiler sich davon die beste Variante
aussucht, unter Berücksichtigung des Optimierungsziels Platz/Zeit.
Die Praxis sagt leider manchmal etwas anderes. So kann es bei avr-gcc
manchmal nützlich sein, die tabellengestützte Implementierung explizit
auszuschliessen.
Sorry, aber laut ANSI-C ist 1==1 ein Bool type, wie auch !0
und 1 ein integer.
ein Beispiel:
#define FALSE 0
#define TRUE !FALSE
#define SWITCH_PORT PORTA
#define SWITCH_BIT 0x30
#define SWITCH_INV 0x30
#define SWITCH (SWITCH_PORT&SWITCH_BIT^SWITCH_INV)
if (SWITCH) { /*todo*/ } // works always
if (SWITCH==TRUE) { /*todo*/ } // works, but
// true was defined as 1, then
if (SWITCH==TRUE) {} // would be expanded as
if ((PORTA&0x30^0x30)==1) {} //and a clever compiler could optimize it
to
if (0) {} // and remove the code, or expand it at runtime always as
false.
Chris wrote:
> Sorry, aber laut ANSI-C ist 1==1 ein Bool type, wie auch !0> und 1 ein integer.
Das kann für traditionelles ANSI-C aka C89 schon deshalb nicht sein,
weil es in da keinen solchen Datentyp gibt.
Nachgesehen: Das stimmt nicht einmal für C99: "The == (equal to) and !=
(not equal to) operators are analogous to the relational 93) operators
except for their lower precedence. Each of the operators yields 1 if the
specified relation is true and 0 if it is false. The result has type
int."
Umgekehrt: Es besteht in C99 ein Unterschied zwischen
bool b;
if (b == 1)
und
int b;
if (b == 1)
weil der erste Fall aufgrund des begrenzten Wertebereichs auch als (b !=
0) implementiert werden darf. Auch hier ist es aber wieder egal, ob da 1
oder !0 steht.
Um mal wieder an den Anfang zu kommen:
> Nun könnte ich bei der if ja einfach schreiben "if(Variable_A)" was mich> daran stört ist dass beide Fälle (True/False) für mich eigentlich> Leitung1 und Leitung2 bedeutet. Deswegen würde ich es der Lesbarkeit> halber lieber so schreiben "if(Variable_A == Leitung1)".
Ich würde (ganz pragmatisch) die Variable "Leitung1" nennen.
Dann könnte ich schreiben:
1
if(Leitung2){}// != 0
bzw.
1
if(!Leitung2){}// das wird dann wohl Leitung 1 sein
>> Dann lässt und gcc 4.f folgenden Code bewundern:>> -- Er nimmt das High-Byte von y und macht einen 8-Bit Wert daraus> -- Aus diesem 8-Bit Wert mach er einen einen signed 16-Bit Wert> -- Er schiebt diesen Wert um 15 nach rechts (Ergebnis ist 0 oder -1)> -- Er maskiert z mit diesem Wert, das Ergebnis ist 0 oder z.>> Klasse! Es werden keine Sprünge mehr gebraucht! Optimal für einen> Boliden wie i386 (Das SBRC ist innerhalb der Komplementbildung, es ist> kein Sprung für den Compiler). Für AVR ist es ne Strafe:>>
1
> foo:
2
> # MSB extrahieren
3
> mov r18,r25
4
> # auf 16 Bit signed expandieren
5
> clr r19
6
> sbrc r18,7
7
> com r19
8
> # Um 15 nach rechts schieben
9
> lsl r19
10
> sbc r18,r18
11
> mov r19,r18
12
> # z mit der Maske verUNDen
13
> and r18,r22
14
> and r19,r23
15
> # return
16
> movw r24,r18
17
> ret
18
>
Hallo,
also mein Compiler erzeugt da was anderes:
int foo (int y, int z)
{
if (y < 0)
ac: 29 2f mov r18, r25
ae: 33 27 eor r19, r19
b0: 27 fd sbrc r18, 7
b2: 30 95 com r19
b4: 33 0f add r19, r19
b6: 22 0b sbc r18, r18
b8: 32 2f mov r19, r18
ba: 26 23 and r18, r22
bc: 37 23 and r19, r23
return z;
return 0;
}
be: c9 01 movw r24, r18
c0: 08 95 ret
Gruß,
Michael
>> Hallo,>> also mein Compiler erzeugt da was anderes:>
1
> ae: 33 27 eor r19, r19
2
> ...
3
> b4: 33 0f add r19, r19
4
> ...
5
>
Zum einen ist das nicht das, was der Compiler erzeugt, sondern die
Ausgabe des Assemblers oder Disassemblers.
Zum zweiten ist CLR X nur syntaktischer Zucker für EOR X,X. Dito LSL X
für ADD X,X.
Das einzige, was nicht von gcc kam, sind meine #-Kommentare