Ist ein Vergleich mit 1 <10 billiger als 1 <1000000?


65

Ich habe gerade ~ 1 Milliarde als Zählung für ein z-indexCSS verwendet und überlegte, welche Vergleiche durchgeführt werden müssen. Gibt es einen Leistungsunterschied auf ALU-Ebene bei Vergleichen zwischen sehr großen und sehr kleinen Zahlen?

Wäre zum Beispiel einer dieser beiden Schnipsel teurer als der andere?

snippet 1

for (int i = 0; i < 10000000; i++){
    if (i < 10000000000000) {
        //do nothing
    }
}

snippet 2

for (int i = 0; i < 10000000; i++){
    if (i < 1000) {
        //do nothing
    }
}


12
OP fragt nicht, wie lange die Verzweigung dauern wird. Es ist klar, dass das Beispiel sicherstellen soll, dass es in beiden Snippets genau die gleiche Zeit benötigt. Die Frage ist, ob die einzelnen CMPMaschinenbefehle langsamer sind, wenn sie igrößer sind.
Kilian Foth

18
Da dies in CSS erfolgt, dominiert die Konvertierung eines Strings in eine Ganzzahl wahrscheinlich die Vergleichsoperation selbst in Bezug auf die Zeit, die für die Ausführung aufgewendet wird.

58
Wenn Sie 1000000000 als Z-Index in einer CSS-Datei verwenden mussten, haben Sie etwas falsch gemacht.
Bergi

6
Bei CSS hängt der Aufwand für die Konvertierung von Text in eine Ganzzahl von der Anzahl der zu konvertierenden Stellen ab (wobei eine 6-stellige Zahl wie 1000000 ungefähr sechsmal so teuer sein kann wie eine 1-stellige Zahl wie 1). und dieser Overhead kann um Größenordnungen größer sein als der Overhead von Ganzzahlvergleichen.
Brendan

Antworten:


82

Jeder Prozessor, an dem ich gearbeitet habe, führt einen Vergleich durch, indem er einen der Operanden vom anderen subtrahiert, das Ergebnis verwirft und die Flags des Prozessors (Null, Negativ usw.) in Ruhe lässt. Da die Subtraktion als einzelne Operation ausgeführt wird, spielt der Inhalt der Operanden keine Rolle.

Der beste Weg, um die Frage sicher zu beantworten, besteht darin, Ihren Code in Assembly zu kompilieren und die generierten Anweisungen in der Dokumentation des Zielprozessors zu finden. Für aktuelle Intel-CPUs wäre dies das Intel 64- und IA-32-Architekturen-Software-Entwicklerhandbuch .

Die Beschreibung der CMPAnweisung ("compare") befindet sich in Band 2A, Seite 3-126 oder Seite 618 der PDF-Datei und beschreibt ihre Funktionsweise als:

temp ← SRC1 − SignExtend(SRC2);
ModifyStatusFlags; (* Modify status flags in the same manner as the SUB instruction*)

Dies bedeutet, dass der zweite Operand bei Bedarf vorzeichenerweitert, vom ersten Operanden subtrahiert und das Ergebnis in einem temporären Bereich im Prozessor abgelegt wird. Dann werden die Status-Flags genauso gesetzt wie bei der SUBAnweisung ("subtrahieren") (Seite 1492 der PDF).

In der Dokumentation CMPoder wird nicht SUBerwähnt, dass sich die Werte der Operanden auf die Latenz auswirken. Daher ist jeder Wert, den Sie verwenden, sicher.


5
Was ist, wenn die Zahl für 32-Bit-Arithmetik zu groß wird? Wäre es dann nicht zu einer langsameren Berechnung aufgeteilt?
Falco

3
@ Falco Nicht auf einer CPU mit einer 64-Bit-ALU (die heutzutage so ziemlich alle außer im eingebetteten Bereich ist.)
reirab

8
@ Falco: Ja, aber da die Frage nach der ALU-Leistung gestellt wird, ist die Implikation, dass die Werte in die Wortgröße der CPU oder in die Fähigkeiten etwaiger SIMD-Anweisungen passen. Der Betrieb mit einer größeren Anzahl als dieser müsste mit mehreren Befehlen außerhalb der CPU implementiert werden. Das war vor 30 Jahren üblich, als Sie nur mit 8- oder 16-Bit-Registern arbeiten mussten.
Blrfl

6
@ Falco Wie würde das Debuggen erfordern? Es ist kein Fehler; Es ist nur ein bisschen langsamer, 64-Bit-Operationen auf einer CPU auszuführen, die 64-Bit-Operationen von Haus aus nicht unterstützt. Es ist ein bisschen lächerlich zu behaupten, man solle niemals eine Zahl über 2 ^ 31-1 verwenden.
Reirab

2
@ Falco Verwenden die Rendering-Engines in Browsern überhaupt Ganzzahlen, um Z-Indizes darzustellen? Die meisten Rendering-Engines Ich kenne mich mit der Verwendung von Floats mit einfacher Genauigkeit für alles aus (bis zur letzten Rasterstufe), habe mich aber nicht wirklich mit Browser-Rendering-Engines befasst.
Reirab

25

Gibt es einen Leistungsunterschied auf ALU-Ebene bei Vergleichen zwischen sehr großen und sehr kleinen Zahlen?

Es ist sehr unwahrscheinlich, es sei denn, der Wechsel von einer kleinen zu einer großen Zahl ändert Ihren numerischen Typ, beispielsweise von einem intzu einem long. Selbst dann ist der Unterschied möglicherweise nicht signifikant. Es ist wahrscheinlicher, dass Sie einen Unterschied feststellen, wenn Ihre Programmiersprache unbemerkt auf Arithmetik mit willkürlicher Genauigkeit umschaltet .

Ihr Compiler führt jedoch möglicherweise einige clevere Optimierungen durch, die Ihnen nicht bekannt sind. Der Weg, den Sie herausfinden, ist zu messen. Führen Sie einen Profiler für Ihren Code aus. Sehen Sie, welche Vergleiche am längsten dauern. Oder starten und stoppen Sie einfach einen Timer.


Es sollte erwähnt werden, dass die vorgeschlagenen Zahlen in der Frage von einem anderen numerischen Typ in einem typischen 32-Bit-Integer-Typ sind ...
Falco

19

Viele Prozessoren haben "kleine" Befehle, mit denen arithmetische Operationen, einschließlich Vergleiche, an bestimmten unmittelbar angegebenen Operanden ausgeführt werden können. Andere Operanden als diese speziellen Werte müssen entweder ein größeres Befehlsformat oder in einigen Fällen einen Befehl "Wert aus dem Speicher laden" verwenden. Im ARM Cortex-M3-Befehlssatz gibt es beispielsweise mindestens fünf Möglichkeiten, wie ein Wert mit einer Konstanten verglichen werden kann:

    cmp r0,#1      ; One-word instruction, limited to values 0-255

    cmp r0,#1000   ; Two-word instruction, limited to values 0-255 times a power of 2

    cmn r0,#1000   ; Equivalent to comparing value with -1000
                   ; Two-word instruction, limited to values 0-255 times a power of 2

    mov r1,#30000  ; Two words; can handle any value 0-65535
    cmp r0,r1      ; Could use cmn to compare to values -1 to -65535

    ldr r1,[constant1000000] ; One or two words, based upon how nearby the constant is
    cmp r0,r1
    ...

constant1000000:
    dd  1000000

Die erste Form ist die kleinste; Die zweite und dritte Form können je nach der Geschwindigkeit des Speichers, aus dem Code abgerufen wird, so schnell ausgeführt werden oder auch nicht. Die vierte Form ist mit ziemlicher Sicherheit langsamer als die ersten drei und die fünfte Form sogar langsamer, aber die letztere kann mit jedem 32-Bit-Wert verwendet werden.

Auf älteren x86-Prozessoren werden Vergleichsbefehle in Kurzform schneller ausgeführt als in Langform. Viele neuere Prozessoren konvertieren jedoch sowohl die Langform als auch die Kurzform in dieselbe Darstellung, wenn sie zum ersten Mal abgerufen werden, und speichern diese einheitliche Darstellung im Cache. Während eingebettete Controller (wie sie auf vielen mobilen Plattformen zu finden sind) einen Geschwindigkeitsunterschied aufweisen, sind dies bei vielen x86-basierten Computern nicht der Fall.

Beachten Sie auch, dass in vielen Fällen, in denen eine Konstante häufig in einer Schleife verwendet wird, ein Compiler die Konstante nur einmal in ein Register laden muss, bevor die Schleife startet. Andererseits gibt es einige Situationen, selbst in kleinen Schleifen, in denen dies nicht immer der Fall ist. Wenn eine Schleife klein ist, aber stark ausgeführt wird, kann es gelegentlich zu einer größeren Leistung zwischen Vergleichen mit kurzen Sofortwerten und Vergleichen mit längeren Werten kommen.


Auf MIPS können Sie nur 16-Bit-Direktzugriffe ausführen, daher ist der Vergleich mit 1 auf jeden Fall kürzer und (wahrscheinlich) schneller als 1000000. Vielleicht auch mit Sparc und PowerPC. Und ich glaube, ich habe aus einigen Quellen gelesen, dass Intel in einigen Fällen auch den Betrieb auf kleinen
Umwegen

@ LưuVĩnhPhúc: Vor der Schleife kann ein Register geladen werden. Zu diesem Zeitpunkt entspricht der tatsächliche Vergleich in beiden Fällen der gleichen Anzahl von Anweisungen.
CHAO

Da die Schleife nur ein Beispiel für die Operation war und die Frage beispielsweise ein Z-Index war, haben Sie 1000 Objekte, jedes mit einem eigenen Z-Index, und Sie setzen sie auf 100000000 ... 1000000999 oder auf 10000 ... 10999 und Sie durchlaufen sie zum Sortieren vor dem Rendern, es gibt viele Vergleiche und viele Ladeanweisungen. Da könnte es einen Unterschied machen!
Falco

@Falco: In diesem Fall würden die Sofortnachrichten nicht einmal berücksichtigen. das Laden und Vergleichen mit einem Register scheint ziemlich unvermeidlich.
CHAO

@cHao: Wenn man Z-Indizes miteinander vergleicht, befinden sie sich in Registern. Wenn man bestimmte Bereiche von Indizes unterschiedlich behandelt, kann dies zu unmittelbaren Vergleichen führen. Normalerweise würden Konstanten geladen, bevor eine Schleife startet, aber wenn man zB eine Schleife hätte, die Wertepaare aus dem Speicher lesen und den ersten Wert jedes Paares mit fünf verschiedenen (nicht gleichmäßig verteilten) Konstanten im Bereich von 100000 vergleichen müsste bis 100499 und der andere Wert mit fünf anderen solchen Konstanten kann es viel schneller sein, 100250 zu subtrahieren (in einem Register gespeichert) und dann mit den Werten -250 bis 250 zu vergleichen ...
supercat

5

Die kurze Antwort auf diese Frage lautet: Nein , es gibt keinen Zeitunterschied, um zwei Zahlen basierend auf der Größe dieser Zahlen zu vergleichen, sofern sie im selben Datentyp gespeichert sind (z. B. beide 32-Bit-Ints oder beide 64-Bit-Longs).

Darüber hinaus ist es bis zur Wortgröße der ALU unglaublich unwahrscheinlich, dass der Vergleich zweier ganzer Zahlen jemals mehr als einen Taktzyklus in Anspruch nimmt, da dies eine triviale Operation ist, die einer Subtraktion entspricht. Ich denke, jede Architektur, mit der ich mich jemals befasst habe, hatte einen Ganzzahlvergleich mit einem Zyklus.

Die einzigen Fälle, an die ich denken kann, bei denen ein Vergleich zweier Zahlen keine Einzeltaktoperation war, sind die folgenden:

  • Anweisungen, bei denen das Abrufen von Operanden tatsächlich eine Speicherlatenz aufweist, die jedoch nichts mit der Funktionsweise des Vergleichs zu tun hat (und bei RISC-Architekturen im Allgemeinen nicht möglich ist, obwohl dies bei CISC-Designs wie x86 / x64 in der Regel möglich ist).
  • Gleitkommavergleiche können je nach Architektur mehrere Zyklen umfassen.
  • Die fraglichen Zahlen passen nicht in die Wortgröße der ALU und daher muss der Vergleich in mehrere Anweisungen aufgeteilt werden.

4

@ RobertHarveys Antwort ist gut; Betrachten Sie diese Antwort als Ergänzung zu seiner.


Sie sollten auch die Branchenvorhersage berücksichtigen :

In der Computerarchitektur ist ein Verzweigungsprädiktor eine digitale Schaltung, die zu erraten versucht, in welche Richtung eine Verzweigung (z. B. eine Wenn-Dann-Sonst-Struktur) gehen wird, bevor dies sicher bekannt ist. Der Zweck des Verzweigungsprädiktors besteht darin, den Fluss in der Anweisungspipeline zu verbessern. In vielen modernen Pipeline-Mikroprozessorarchitekturen wie x86 spielen Verzweigungsvorhersagen eine entscheidende Rolle für die Erzielung einer hohen effektiven Leistung.

Wenn in Ihrem Beispiel die ifAnweisung in der Schleife immer dieselbe Antwort zurückgibt, kann das System sie optimieren, indem es richtig errät, in welche Richtung sie verzweigt. Da in Ihrem Beispiel die ifAnweisung im ersten Fall immer das gleiche Ergebnis zurückgibt, wird sie etwas schneller ausgeführt als im zweiten Fall.

Ausgezeichnete Stapelüberlauffrage zum Thema


Die Verzweigungsvorhersage wirkt sich auf die Verzweigungszeit aus, nicht jedoch auf die Vergleichszeit.
Reirab

3

Das hängt von der Implementierung ab, ist aber sehr, sehr unwahrscheinlich .

Ich gebe zu, dass ich die Implementierungsdetails der verschiedenen Browser-Engines nicht durchgelesen habe und CSS keinen bestimmten Speichertyp für Zahlen spezifiziert. Ich bin jedoch der Meinung, dass davon ausgegangen werden kann, dass alle gängigen Browser 64-Bit-Gleitkommazahlen mit doppelter Genauigkeit ("Doubles") verwenden, um einen Begriff aus C / C ++ auszuleihen und die meisten ihrer numerischen Anforderungen in CSS zu erfüllen , weil JavaScript dies für Zahlen verwendet und daher die Integration durch Verwendung desselben Typs erleichtert.

Vom Standpunkt des Computers aus tragen alle Doubles dieselbe Datenmenge: 64 Bit, unabhängig davon, ob der Wert 1 oder -3,14 oder 1000000 oder 1e100 ist . Die Zeit, die für eine Operation mit diesen Zahlen benötigt wird, hängt nicht vom tatsächlichen Wert dieser Zahlen ab, da immer dieselbe Datenmenge verarbeitet wird. Es ist ein Kompromiss, Dinge auf diese Weise zu tun, da Doppelte nicht alle Zahlen (oder sogar alle Zahlen in ihrem Bereich) genau darstellen können, aber für die meisten Angelegenheiten nahe genug kommen können und die Art der Dinge, die CSS nicht numerisch tut -Anforderung genug, um mehr Präzision als das zu brauchen. Kombinieren Sie dies mit den Vorteilen der direkten Kompatibilität mit JavaScript, und Sie haben ein ziemlich starkes Argument für Doppel.

Es ist nicht unmöglich, dass jemand CSS mithilfe einer Codierung variabler Länge für Zahlen implementiert. Wenn jemand eine Codierung mit variabler Länge verwendet, dann gegen kleine Zahlen zu vergleichen wäre weniger teuer als Vergleich gegen eine große Zahl, weil eine große Zahl mehr Daten knirschen . Diese Art der Codierung kann präziser als die Binärcodierung sein, sie ist jedoch auch viel langsamer, und insbesondere für CSS reichen die Genauigkeitsgewinne wahrscheinlich nicht aus, um den Leistungseffekt zu erzielen. Ich wäre sehr überrascht zu erfahren, dass jeder Browser die Dinge so gemacht hat.

Theoretisch gibt es zu allem, was ich oben gesagt habe, eine mögliche Ausnahme: Der Vergleich mit Null ist oft schneller als der Vergleich mit anderen Zahlen . Das liegt nicht daran, dass Null kurz ist (wenn das der Grund wäre, dann sollte 1 genauso schnell sein, aber es ist nicht so). Es liegt daran, dass Sie mit Null betrügen können. Es ist die einzige Zahl, bei der alle Bits deaktiviert sind. Wenn Sie also wissen, dass einer der Werte Null ist, müssen Sie den anderen Wert nicht einmal als Zahl betrachten: Wenn eines der Bits aktiviert ist, ist es nicht gleich Null, und dann müssen Sie nur ein Bit betrachten, um festzustellen, ob es größer oder kleiner als Null ist.


0

Wenn dieser Code jedes Mal interpretiert wird, wenn er ausgeführt wird, gibt es einen Unterschied, da das Tokenisieren und Interpretieren im 10000000000000Vergleich zu länger dauert 1000. Dies ist jedoch die offensichtliche erste Optimierung der Interpreter in diesem Fall: einmal tokenisieren und die Token interpretieren.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.