Ist if( a < 901 )
schneller als if( a <= 900 )
.
Nicht genau wie in diesem einfachen Beispiel, aber es gibt geringfügige Leistungsänderungen bei komplexem Schleifencode. Ich nehme an, dass dies etwas mit dem generierten Maschinencode zu tun hat, falls es überhaupt wahr ist.
<
ist zweimal schneller als das Tippen<=
.Antworten:
Nein, auf den meisten Architekturen wird es nicht schneller sein. Sie haben nicht angegeben, aber auf x86 werden alle integralen Vergleiche normalerweise in zwei Maschinenanweisungen implementiert:
test
odercmp
Anweisung, die setztEFLAGS
Jcc
(Sprung-) Anweisung , abhängig vom Vergleichstyp (und Code-Layout):jne
- Springe wenn nicht gleich ->ZF = 0
jz
- Springe wenn Null (gleich) ->ZF = 1
jg
- Springe wenn größer ->ZF = 0 and SF = OF
Beispiel (der Kürze halber bearbeitet) Kompiliert mit
$ gcc -m32 -S -masm=intel test.c
Kompiliert zu:
Und
Kompiliert zu:
Der einzige Unterschied zwischen den beiden ist also
jg
einejge
Anweisung. Die beiden werden die gleiche Zeit in Anspruch nehmen.Ich möchte auf den Kommentar eingehen, dass nichts darauf hinweist, dass die verschiedenen Sprunganweisungen dieselbe Zeit in Anspruch nehmen. Diese Frage ist etwas schwierig zu beantworten, aber ich kann Folgendes geben: In der Intel-Befehlssatzreferenz sind sie alle unter einer gemeinsamen Anweisung zusammengefasst
Jcc
(Springen, wenn die Bedingung erfüllt ist). Dieselbe Gruppierung wird im Optimierungsreferenzhandbuch in Anhang C zusammengefasst. Latenz und Durchsatz.Die Werte für
Jcc
sind:mit folgender Fußnote zu
Jcc
:Nichts in den Intel-Dokumenten behandelt eine
Jcc
Anweisung jemals anders als die anderen.Wenn man über die tatsächliche Schaltung nachdenkt, die zum Implementieren der Anweisungen verwendet wird, kann man annehmen, dass es einfache UND / ODER-Gatter auf den verschiedenen Bits in gibt
EFLAGS
, um zu bestimmen, ob die Bedingungen erfüllt sind. Es gibt dann keinen Grund, warum ein Befehl, der zwei Bits testet, mehr oder weniger Zeit in Anspruch nehmen sollte als ein Befehl, der nur eines testet (Ignorieren der Gate-Ausbreitungsverzögerung, die viel kürzer als die Taktperiode ist).Bearbeiten: Gleitkomma
Dies gilt auch für x87-Gleitkommazahlen: (Ziemlich derselbe Code wie oben, jedoch mit
double
stattint
.)quelle
jg
undjnle
sind die gleiche Anweisung,7F
:-)Historisch gesehen (wir sprechen von den 1980er und frühen 1990er Jahren) gab es einige Architekturen, in denen dies zutraf. Das Hauptproblem besteht darin, dass der Ganzzahlvergleich von Natur aus über Ganzzahlsubtraktionen implementiert wird. Dies führt zu folgenden Fällen.
Nun, wenn
A < B
die Subtraktion ein High-Bit ausleihen muss, damit die Subtraktion korrekt ist, so wie Sie es beim Addieren und Subtrahieren von Hand tragen und ausleihen. Dieses "geliehene" Bit wurde üblicherweise als Übertragsbit bezeichnet und kann durch einen Verzweigungsbefehl getestet werden. Ein zweites Bit, das als Nullbit bezeichnet wird, würde gesetzt, wenn die Subtraktion identisch Null wäre, was Gleichheit impliziert.Es gab normalerweise mindestens zwei bedingte Verzweigungsbefehle, einen zum Verzweigen auf dem Übertragsbit und einen zum Nullbit.
Um auf den Punkt zu kommen, erweitern wir die vorherige Tabelle um die Übertragungs- und Null-Bit-Ergebnisse.
Das Implementieren einer Verzweigung für
A < B
kann also in einem Befehl erfolgen, da das Übertragsbit nur in diesem Fall klar ist, d. H.Wenn wir jedoch einen Vergleich durchführen möchten, der kleiner oder gleich ist, müssen wir das Null-Flag zusätzlich überprüfen, um den Fall der Gleichheit zu erfassen.
Also, auf einigen Maschinen mit einem „kleiner als“ Vergleich könnte speichert einen Maschinenbefehl . Dies war im Zeitalter der Sub-Megahertz-Prozessorgeschwindigkeit und des Verhältnisses von CPU zu Speicher von 1: 1 relevant, ist aber heute fast völlig irrelevant.
quelle
jge
, die sowohl das Null- als auch das Vorzeichen- / Übertragsflag testen.<=
Test in einem Befehl implementiert werden , wobei die Operanden ausgetauscht werden und aufnot <
(äquivalent zu>=
) getestet wird. Dies ist<=
bei vertauschten Operanden erwünscht :cmp B,A; bcs addr
. Das ist der Grund, warum dieser Test von Intel weggelassen wurde, sie hielten ihn für überflüssig und man konnte sich damals keine redundanten Anweisungen leisten :-)Angenommen, es handelt sich um interne Ganzzahltypen, gibt es keine Möglichkeit, dass einer schneller als der andere sein könnte. Sie sind offensichtlich semantisch identisch. Beide fordern den Compiler auf, genau dasselbe zu tun. Nur ein schrecklich kaputter Compiler würde für einen davon minderwertigen Code generieren.
Wenn es eine Plattform war , wo
<
war schneller als<=
für einfachen Integer - Typen, sollte der Compiler immer konvertieren ,<=
um<
für Konstanten. Jeder Compiler, der dies nicht tat, wäre nur ein schlechter Compiler (für diese Plattform).quelle
<
noch<=
Geschwindigkeit, bis der Compiler entscheidet, welche Geschwindigkeit er haben wird. Dies ist eine sehr einfache Optimierung für Compiler, wenn Sie bedenken, dass sie im Allgemeinen bereits eine Deadcode-Optimierung, eine Tail-Call-Optimierung, ein Loop-Heben (und gelegentlich das Abrollen), eine automatische Parallelisierung verschiedener Loops usw. durchführen. Warum Zeit damit verschwenden, über vorzeitige Optimierungen nachzudenken? ? Lassen Sie einen Prototyp laufen, profilieren Sie ihn, um festzustellen, wo die wichtigsten Optimierungen liegen, führen Sie diese Optimierungen in der Reihenfolge ihrer Bedeutung durch und profilieren Sie sie erneut, um den Fortschritt zu messen ...(a < C)
zu(a <= C-1)
(für eine KonstanteC
) dieC
Codierung im Befehlssatz schwieriger macht. Beispielsweise kann ein Befehlssatz in Vergleichen vorzeichenbehaftete Konstanten von -127 bis 128 in kompakter Form darstellen, Konstanten außerhalb dieses Bereichs müssen jedoch entweder mit einer längeren, langsameren Codierung oder einem anderen Befehl vollständig geladen werden. Ein Vergleich wie dieser hat also(a < -127)
möglicherweise keine einfache Transformation.a > 127
,a > 128
weil Sie dort keine Wahl haben, sondern die verwenden, die Sie benötigen. Wir vergleichena > 127
mita >= 128
, die keine unterschiedliche Codierung oder unterschiedliche Anweisungen erfordern können, da sie dieselbe Wahrheitstabelle haben. Jede Codierung von einer ist gleichermaßen eine Codierung von der anderen.<=
um<
für Konstanten“. Soweit ich weiß, beinhaltet diese Transformation das Ändern der Konstante. ZBa <= 42
wird kompiliert,a < 43
weil<
es schneller ist. In einigen Randfällen wäre eine solche Transformation nicht fruchtbar, da die neue Konstante möglicherweise mehr oder langsamere Anweisungen erfordert. Natürlicha > 127
unda >= 128
sind gleichwertig und ein Compiler sollte beide Formulare auf die (gleiche) schnellste Weise codieren, aber das ist nicht unvereinbar mit dem, was ich gesagt habe.Ich sehe, dass keiner schneller ist. Der Compiler generiert in jeder Bedingung denselben Maschinencode mit einem anderen Wert.
Mein Beispiel
if
stammt von GCC auf der x86_64-Plattform unter Linux.Compiler-Autoren sind ziemlich kluge Leute, und sie denken an diese und viele andere Dinge, die die meisten von uns für selbstverständlich halten.
Ich habe festgestellt, dass in beiden Fällen der gleiche Maschinencode generiert wird, wenn es sich nicht um eine Konstante handelt.
quelle
if(a <=900)
zu demonstrieren, dass es genau das gleiche asm erzeugt :)Für Gleitkomma-Code kann der Vergleich <= sogar auf modernen Architekturen langsamer sein (um einen Befehl). Hier ist die erste Funktion:
Auf PowerPC führt dies zuerst einen Gleitkomma-Vergleich durch (der
cr
das Bedingungsregister aktualisiert ), verschiebt dann das Bedingungsregister in einen GPR, verschiebt das Bit "verglichen weniger als" an seinen Platz und kehrt dann zurück. Es dauert vier Anweisungen.Betrachten Sie nun stattdessen diese Funktion:
Dies erfordert die gleiche Arbeit wie
compare_strict
oben, aber jetzt gibt es zwei interessante Punkte: "war kleiner als" und "war gleich". Dies erfordert einen zusätzlichen Befehl (cror
- Bedingungsregister bitweise ODER), um diese beiden Bits zu einem zu kombinieren. Socompare_loose
erfordert fünf Anweisungen, währendcompare_strict
vier erfordert.Sie könnten denken, dass der Compiler die zweite Funktion folgendermaßen optimieren könnte:
Dies behandelt jedoch NaNs falsch.
NaN1 <= NaN2
undNaN1 > NaN2
müssen beide zu falsch bewerten.quelle
fucomip
setzt ZF und CF.cr
ist das Äquivalent zu Flags wieZF
undCF
auf der x86. (Obwohl die CR flexibler ist.) Das Poster spricht davon, das Ergebnis in einen GPR zu verschieben: Dies erfordert zwei Anweisungen auf PowerPC, aber x86 verfügt über eine bedingte Verschiebungsanweisung.Vielleicht hat der Autor dieses unbenannten Buches gelesen, dass es
a > 0
schneller läuft alsa >= 1
und denkt, dass dies universell wahr ist.Aber es liegt daran, dass a
0
beteiligt ist (weilCMP
je nach Architektur zB durch ersetzt werden kannOR
) und nicht an der<
.quelle
(a >= 1)
(a > 0)
Wenn dies wahr wäre, könnte ein Compiler zumindest a <= b bis! (A> b) trivial optimieren, und selbst wenn der Vergleich selbst tatsächlich langsamer wäre, würden Sie mit allen außer dem naivsten Compiler keinen Unterschied bemerken .
quelle
NOT
wird nur durch andere Anweisung (je
vs.jne
) gemachtSie haben die gleiche Geschwindigkeit. Vielleicht ist in einer speziellen Architektur das, was er / sie gesagt hat, richtig, aber zumindest in der x86-Familie weiß ich, dass sie gleich sind. Zu diesem Zweck führt die CPU eine Subtraktion (a - b) durch und überprüft dann die Flags des Flagregisters. Zwei Bits dieses Registers heißen ZF (Null-Flag) und SF (Vorzeichen-Flag) und werden in einem Zyklus ausgeführt, da dies mit einer Maskenoperation erfolgt.
quelle
Dies hängt stark von der zugrunde liegenden Architektur ab, zu der das C kompiliert wird. Einige Prozessoren und Architekturen verfügen möglicherweise über explizite Anweisungen für gleich oder kleiner als und gleich, die in unterschiedlicher Anzahl von Zyklen ausgeführt werden.
Das wäre allerdings ziemlich ungewöhnlich, da der Compiler es umgehen könnte, was es irrelevant macht.
quelle
TL; DR Antwort
Bei den meisten Kombinationen aus Architektur, Compiler und Sprache ist dies nicht schneller.
Vollständige Antwort
Andere Antworten werden auf konzentriert x86 - Architektur, und ich weiß nicht , die ARM - Architektur gut genug , um einen Kommentar speziell auf dem Code erzeugt, aber dies ist ein Beispiel für eine (die Ihr Beispiel Assembler zu sein scheint) Mikro-Optimierung , die ist sehr Architektur spezifisch und ist ebenso wahrscheinlich eine Anti-Optimierung wie eine Optimierung .
Daher würde ich vorschlagen, dass diese Art der Mikrooptimierung eher ein Beispiel für die Frachtkultprogrammierung als für die beste Softwareentwicklungspraxis ist.
Es gibt wahrscheinlich einige Architekturen, bei denen dies eine Optimierung ist, aber ich kenne mindestens eine Architektur, bei der das Gegenteil der Fall sein kann. Die ehrwürdige Transputer- Architektur hatte nur Maschinencode-Anweisungen für gleich und größer als oder gleich , so dass alle Vergleiche aus diesen Grundelementen erstellt werden mussten.
Selbst dann konnte der Compiler in fast allen Fällen die Auswertungsanweisungen so anordnen, dass in der Praxis kein Vergleich einen Vorteil gegenüber einem anderen hatte. Im schlimmsten Fall muss möglicherweise eine umgekehrte Anweisung (REV) hinzugefügt werden, um die beiden obersten Elemente auf dem Operandenstapel auszutauschen . Dies war ein Einzelbyte-Befehl, dessen Ausführung einen einzelnen Zyklus dauerte und daher den geringstmöglichen Overhead aufwies.
Unabhängig davon , ob eine Mikro-Optimierung wie dies ist eine Optimierung oder eine anti-Optimierung auf der spezifische Architektur ab , die Sie verwenden, so ist es in der Regel eine schlechte Idee ist , in die Gewohnheit, von Architektur spezifische Mikro-Optimierungen verwenden, sonst könnte man instinktiv Verwenden Sie eine, wenn dies unangemessen ist, und es sieht so aus, als würde das Buch, das Sie lesen, genau dies befürworten.
quelle
Sie sollten den Unterschied nicht bemerken können, selbst wenn es einen gibt. Außerdem müssen Sie in der Praxis eine zusätzliche
a + 1
odera - 1
eine Bedingung ausführen, es sei denn, Sie verwenden einige magische Konstanten, was auf jeden Fall eine sehr schlechte Praxis ist.quelle
Man könnte sagen, dass die Zeile in den meisten Skriptsprachen korrekt ist, da das zusätzliche Zeichen zu einer etwas langsameren Codeverarbeitung führt. Wie in der Top-Antwort bereits erwähnt, sollte dies in C ++ keine Auswirkungen haben, und alles, was mit einer Skriptsprache ausgeführt wird, ist wahrscheinlich nicht so wichtig für die Optimierung.
quelle
Als ich diese Antwort schrieb, war ich auf der Suche nur auf der Titel - Frage zu <vs. <= in der Regel nicht das spezifische Beispiel eines konstanten
a < 901
vs.a <= 900
. Viele Compiler verkleinern die Größe von Konstanten immer durch Konvertieren zwischen<
und<=
, z. B. weil der x86-Sofortoperand eine kürzere 1-Byte-Codierung für -128..127 hat.Für ARM und insbesondere AArch64 hängt die Fähigkeit, sofort zu codieren, davon ab, dass ein schmales Feld in eine beliebige Position in einem Wort gedreht werden kann. Also
cmp w0, #0x00f000
wäre kodierbar,cmp w0, #0x00effff
könnte aber nicht sein. Daher gilt die AA-Regel zum Vergleich mit einer Konstante zur Kompilierungszeit nicht immer für AArch64.<vs. <= im Allgemeinen, auch für Bedingungen mit Laufzeitvariablen
In der Assemblersprache der meisten Maschinen hat ein Vergleich für
<=
die gleichen Kosten wie ein Vergleich für<
. Dies gilt unabhängig davon, ob Sie darauf verzweigen, es boolesch machen, um eine 0/1-Ganzzahl zu erstellen, oder es als Prädikat für eine verzweigungslose Auswahloperation (wie x86 CMOV) verwenden. Die anderen Antworten haben nur diesen Teil der Frage angesprochen.Bei dieser Frage geht es jedoch um die C ++ - Operatoren, die Eingabe in den Optimierer. Normalerweise sind beide gleich effizient. Der Rat aus dem Buch klingt völlig falsch, da Compiler den Vergleich, den sie in asm implementieren, immer transformieren können. Es gibt jedoch mindestens eine Ausnahme, bei der die Verwendung
<=
versehentlich etwas erzeugen kann, das der Compiler nicht optimieren kann.Als Schleifenbedingung gibt es Fälle, in denen
<=
sich der Compiler qualitativ davon unterscheidet<
, zu beweisen, dass eine Schleife nicht unendlich ist. Dies kann einen großen Unterschied machen und die automatische Vektorisierung deaktivieren.Der vorzeichenlose Überlauf ist im Gegensatz zum vorzeichenbehafteten Überlauf (UB) als Base-2-Wrap-Around gut definiert. Vorzeichenbehaftete Schleifenzähler sind im Allgemeinen davor sicher, da Compiler, die basierend auf dem nicht auftretenden UB mit vorzeichenbehaftetem Überlauf optimieren, nicht
++i <= size
irgendwann falsch werden. ( Was jeder C-Programmierer über undefiniertes Verhalten wissen sollte )Compiler können nur so optimieren, dass das (definierte und rechtlich beobachtbare) Verhalten der C ++ - Quelle für alle möglichen Eingabewerte erhalten bleibt , mit Ausnahme derjenigen, die zu undefiniertem Verhalten führen.
(Ein einfaches
i <= size
würde auch das Problem verursachen, aber ich dachte, die Berechnung einer Obergrenze wäre ein realistischeres Beispiel für die versehentliche Einführung der Möglichkeit einer Endlosschleife für eine Eingabe, die Sie nicht interessieren, die der Compiler jedoch berücksichtigen muss.)In diesem Fall
size=0
führtupper_bound=UINT_MAX
undi <= UINT_MAX
ist immer wahr. Diese Schleife ist also unendlich fürsize=0
, und der Compiler muss dies respektieren, obwohl Sie als Programmierer wahrscheinlich nie beabsichtigen, size = 0 zu übergeben. Wenn der Compiler diese Funktion in einen Aufrufer einbinden kann, in dem er beweisen kann, dass size = 0 unmöglich ist, kann er großartig optimieren, wie es für möglich wärei < size
.Asm like
if(!size) skip the loop;
do{...}while(--size);
ist eine normalerweise effiziente Methode zur Optimierung einerfor( i<size )
Schleife, wenn der tatsächliche Wert voni
innerhalb der Schleife nicht benötigt wird ( Warum werden Schleifen immer im Stil "do ... while" kompiliert (Tail Jump)? ).Aber das kann nicht unendlich sein: Wenn
size==0
wir mit eingeben, erhalten wir 2 ^ n Iterationen. (Das Iterieren über alle vorzeichenlosen Ganzzahlen in einer for-Schleife C ermöglicht es, eine Schleife über alle vorzeichenlosen Ganzzahlen einschließlich Null auszudrücken, aber ohne ein Übertragsflag ist es nicht einfach, wie es in asm ist.)Da der Umlauf des Schleifenzählers möglich ist, geben moderne Compiler oft nur auf und optimieren nicht annähernd so aggressiv.
Beispiel: Summe von ganzen Zahlen von 1 bis n
Verwenden von vorzeichenlosen
i <= n
Niederlagen Clangs Redewendung, diesum(1 .. n)
Schleifen mit einer geschlossenen Form basierend auf der Gaußschenn * (n+1) / 2
Formel optimiert .x86-64 asm von clang7.0 und gcc8.2 im Godbolt-Compiler-Explorer
Aber für die naive Version bekommen wir nur eine dumme Schleife von Clang.
GCC verwendet in keiner Weise eine geschlossene Form, so dass die Wahl der Schleifenbedingung nicht wirklich schadet . Es wird automatisch mit einer SIMD-Ganzzahladdition vektorisiert, wobei 4
i
Werte parallel in den Elementen eines XMM-Registers ausgeführt werden.Es hat auch eine einfache Skalarschleife, die meiner Meinung nach für sehr kleine
n
und / oder für den Endlosschleifenfall verwendet wird.Übrigens verschwenden diese beiden Schleifen einen Befehl (und einen UOP auf CPUs der Sandybridge-Familie) für den Schleifen-Overhead.
sub eax,1
/jnz
anstelle vonadd eax,1
/ cmp / jcc wäre effizienter. 1 uop statt 2 (nach Makrofusion von sub / jcc oder cmp / jcc). Der Code nach beiden Schleifen schreibt EAX bedingungslos, sodass nicht der Endwert des Schleifenzählers verwendet wird.quelle
<
oder<=
. Aber sicher,test ecx,ecx
/bt eax, 3
/jbe
springt , wenn ZF gesetzt (ECX == 0) oder wenn CF gesetzt (Bit 3 von EAX == 1), was zu einem teilweisen Flag Stall auf den meisten CPUs , da die Fahnen es tun liest nicht alle kommen aus der letzten Anweisung, um irgendwelche Flags zu schreiben. Bei der Sandybridge-Familie kommt es nicht wirklich zum Stillstand, sondern muss nur ein verschmelzendes Uop einfügen.cmp
Ichtest
schreibe alle Flags,bt
lasse aber ZF unverändert. felixcloutier.com/x86/btNur wenn die Leute, die die Computer erstellt haben, schlecht mit boolescher Logik umgehen können. Was sie nicht sein sollten.
Jeder Vergleich (
>=
<=
>
<
) kann mit der gleichen Geschwindigkeit durchgeführt werden.Was jeder Vergleich ist, ist nur eine Subtraktion (der Unterschied) und zu sehen, ob er positiv / negativ ist.
(Wenn das
msb
eingestellt ist, ist die Zahl negativ)Wie überprüfe ich
a >= b
? Suba-b >= 0
Überprüfen Sie, oba-b
positiv ist.Wie überprüfe ich
a <= b
? Sub0 <= b-a
Überprüfen Sie, obb-a
positiv ist.Wie überprüfe ich
a < b
? Suba-b < 0
Überprüfen Sie, oba-b
negativ ist.Wie überprüfe ich
a > b
? Sub0 > b-a
Überprüfen Sie, obb-a
negativ ist.Einfach ausgedrückt, der Computer kann dies einfach unter der Haube für die gegebene Operation tun:
a >= b
==msb(a-b)==0
a <= b
==msb(b-a)==0
a > b
==msb(b-a)==1
a < b
==msb(a-b)==1
und natürlich würde der Computer das
==0
oder auch==1
nicht tun müssen .für das
==0
könnte es einfach dasmsb
von der schaltung umkehren.Wie auch immer, sie hätten es mit Sicherheit nicht
a >= b
alsa>b || a==b
lol berechnetquelle