Ist "WENN" teuer?


98

Ich kann mich für mein ganzes Leben nicht erinnern, was genau unser Lehrer an diesem Tag gesagt hat, und ich hoffe, Sie würden es wahrscheinlich wissen.

Das Modul ist "Datenstrukturen und Algorithmen" und er erzählte uns etwas in der Art von:

Die ifAussage ist das teuerste [etwas]. [etwas] registriert [etwas].

Ja, ich habe ein schreckliches Gedächtnis und es tut mir wirklich sehr leid, aber ich habe stundenlang gegoogelt und es ist nichts aufgetaucht. Irgendwelche Ideen?


29
Ist es eine Option, Ihren Lehrer zu fragen?
Michael Myers

7
Warum schickst du deinem Lehrer keine E-Mail? Es ist unwahrscheinlich, dass jemand auf SO weiß, was Ihr Lehrer gesagt hat, es sei denn, er war zu diesem Zeitpunkt dort (oder Ihr Lehrer selbst liest SO).
Bill Karwin

11
Und natürlich ein Link zur obligatorischen Antwort
Bobobobo

If-Anweisungen oder insbesondere "?:" - Ausdrücke in C-beeinflussten Curly-Bracket-Sprachen können durch spezielle Anweisungen zur bedingten Ausführung auf z. B. x86- und Arm-Prozessoren implementiert werden. Dies sind Anweisungen, die einen Vorgang basierend auf einem vorherigen Test ausführen oder nicht ausführen. Durch die Verwendung dieser hervorragenden Anweisungen wird die Notwendigkeit von Anweisungen für bedingte Sprünge / Verzweigungen / "Gehe zu" insgesamt vermieden. Eine enorme Leistungsverbesserung in einigen Situationen, indem der Programmfluss vollständig vorhersehbar gemacht wird, da er gerade weiterläuft, ohne (möglicherweise unvorhersehbar) zu verschiedenen Punkten im Code zu springen.
Cecil Ward

Ein guter Compiler benötigt manchmal einen kleinen Druck in die richtige Richtung, damit er bedingte Anweisungen verwendet, anstatt dumm zu sein und bedingte Sprünge zu verwenden, indem er Code neu organisiert und möglicherweise eine clevere Arithmetik in einem Ausdruck oder einem? : Ausdruck. Spielen Sie nicht damit, es sei denn, Sie kennen Ihren Asm wirklich und haben zB die Optimierungshandbücher von Agner Fog gelesen. Compiler machen es manchmal richtig, egal ob if-Anweisungen oder? : Ausdrücke werden verwendet.
Cecil Ward

Antworten:


185

Auf der untersten Ebene (in der Hardware), ja, wenn s teuer sind. Um zu verstehen, warum, müssen Sie verstehen, wie Pipelines funktionieren.

Der aktuell auszuführende Befehl wird in etwas gespeichert, das typischerweise als Befehlszeiger (IP) oder Programmzähler (PC) bezeichnet wird. Diese Begriffe sind synonym, aber unterschiedliche Begriffe werden mit unterschiedlichen Architekturen verwendet. Bei den meisten Anweisungen ist der PC des nächsten Befehls nur der aktuelle PC plus die Länge des aktuellen Befehls. Bei den meisten RISC-Architekturen haben alle Anweisungen eine konstante Länge, sodass der PC um einen konstanten Betrag erhöht werden kann. Bei CISC-Architekturen wie x86 können Befehle eine variable Länge haben. Daher muss die Logik, die den Befehl decodiert, herausfinden, wie lange der aktuelle Befehl dauert, um den Ort des nächsten Befehls zu finden.

Bei Verzweigungsbefehlen ist der nächste auszuführende Befehl jedoch nicht der nächste Ort nach dem aktuellen Befehl. Zweige sind gotos - sie teilen dem Prozessor mit, wo der nächste Befehl ist. Zweige können entweder bedingt oder bedingungslos sein, und der Zielort kann entweder fest oder berechnet sein.

Bedingt gegen bedingungslos ist leicht zu verstehen - ein bedingter Zweig wird nur genommen, wenn eine bestimmte Bedingung erfüllt ist (z. B. ob eine Zahl einer anderen entspricht); Wenn die Verzweigung nicht genommen wird, fährt die Steuerung wie gewohnt mit der nächsten Anweisung nach der Verzweigung fort. Bei bedingungslosen Verzweigungen wird immer die Verzweigung verwendet. Bedingte Verzweigungen erscheinen in ifAussagen und Kontrolltests vonfor und whileSchleifen angezeigt. Unbedingte Verzweigungen werden in Endlosschleifen, Funktionsaufrufen, Funktionsrückgaben breakund continueAnweisungen, der berüchtigten gotoAnweisung und vielem mehr angezeigt (diese Listen sind alles andere als vollständig).

Das Zweigziel ist ein weiteres wichtiges Thema. Die meisten Zweige haben ein festes Zweigziel - sie gehen zu einem bestimmten Ort im Code, der zur Kompilierungszeit festgelegt ist. Das beinhaltetif Anweisungen, Schleifen aller Art, regelmäßige Funktionsaufrufe und vieles mehr. Berechnete Verzweigungen berechnet das Ziel der Verzweigung zur Laufzeit. Dies umfasst switch(manchmal) Anweisungen, die von einer Funktion zurückkehren, virtuelle Funktionsaufrufe und Funktionszeigeraufrufe.

Was bedeutet das alles für die Leistung? Wenn der Prozessor einen Verzweigungsbefehl in seiner Pipeline sieht, muss er herausfinden, wie er seine Pipeline weiter füllen kann. Um herauszufinden, welche Anweisungen nach der Verzweigung im Programmstrom kommen, muss es zwei Dinge wissen: (1) ob die Verzweigung genommen wird und (2) das Ziel der Verzweigung. Dies herauszufinden heißt Verzweigungsvorhersage bezeichnet und ist ein herausforderndes Problem. Wenn der Prozessor richtig vermutet, wird das Programm mit voller Geschwindigkeit fortgesetzt. Wenn der Prozessor stattdessen falsch vermutet , hat er nur einige Zeit damit verbracht, das Falsche zu berechnen. Es muss nun seine Pipeline leeren und sie mit Anweisungen aus dem richtigen Ausführungspfad neu laden. Fazit: ein großer Performance-Hit.

Der Grund, warum Aussagen teuer sind, liegt in falschen Vorhersagen der Branche . Dies ist nur auf der niedrigsten Ebene. Wenn Sie Code auf hoher Ebene schreiben, müssen Sie sich um diese Details überhaupt nicht kümmern. Sie sollten sich nur darum kümmern, wenn Sie extrem leistungskritischen Code in C oder Assembly schreiben. In diesem Fall kann das Schreiben von verzweigungsfreiem Code dem verzweigten Code häufig überlegen sein, selbst wenn mehrere weitere Anweisungen erforderlich sind. Es gibt ein paar coole Bit-twiddling Tricks , die Sie Dinge wie berechnen tun können abs(), min()und max()ohne Verzweigung.


20
Es sind nicht nur falsche Vorhersagen. Verzweigungen verhindern auch die Neuordnung von Befehlen auf Compilerebene und in gewissem Maße auch auf CPU-Ebene (natürlich für eine CPU außerhalb der Reihenfolge). Schöne detaillierte Antwort.
Jalf

4
Wenn Hochsprachen letztendlich in Niedrigsprachen übersetzt werden und Sie sehr leistungsorientierten Code schreiben, gewinnen Sie dann immer noch nichts, wenn Sie Code schreiben, der if-Anweisungen vermeidet? Gilt dieses Konzept nicht für höhere Sprachen?
c ..

18

"Teuer" ist ein sehr relativer Begriff, insbesondere in Bezug auf eine " if" Aussage, da Sie auch die Kosten der Bedingung berücksichtigen müssen. Dies kann von einigen kurzen CPU-Anweisungen bis zum Testen des Ergebnisses einer Funktion reichen, die eine entfernte Datenbank aufruft.

Ich würde mir darüber keine Sorgen machen. Wenn Sie keine eingebettete Programmierung durchführen, sollten Sie sich wahrscheinlich überhaupt keine Gedanken über die Kosten von " if" machen. Für die meisten Programmierer wird dies niemals der treibende Faktor für die Leistung Ihrer App sein.


1
Auf jeden Fall relativ ... cmp / cond jmp ist auf vielen Prozessoren immer noch schneller als ein Mul.
Brian Knoblauch

4
Ja, ich stimme zu, dass ich mir darüber keine Sorgen machen sollte. Ich versuche hier nichts zu optimieren. Ich versuche nur herauszufinden und zu lernen. ;)
Pek

15

Zweige, insbesondere auf Mikroprozessoren mit RISC-Architektur, gehören zu den teuersten Anweisungen. Dies liegt daran, dass der Compiler auf vielen Architekturen vorhersagt, welcher Ausführungspfad am wahrscheinlichsten genommen wird, und diese Anweisungen als nächstes in die ausführbare Datei einfügt, sodass sie sich bereits im CPU-Cache befinden, wenn die Verzweigung erfolgt. Wenn der Zweig in die andere Richtung geht, muss er wieder in den Hauptspeicher zurückkehren und die neuen Anweisungen abrufen - das ist ziemlich teuer. Bei vielen RISC-Architekturen bestehen alle Anweisungen aus einem Zyklus, mit Ausnahme der Verzweigung (häufig aus zwei Zyklen). Wir sprechen hier nicht über große Kosten, also mach dir keine Sorgen. Außerdem optimiert der Compiler in 99% der Fälle besser als Sie: ) Eines der wirklich großartigen Dinge an der EPIC-Architektur (Itanium ist ein Beispiel) ist, dass sie Anweisungen von beiden Seiten des Zweigs zwischenspeichert (und mit der Verarbeitung beginnt) und dann den Satz verwirft, den sie nicht benötigt, sobald das Ergebnis des Zweigs vorliegt bekannt. Dies spart den zusätzlichen Speicherzugriff einer typischen Architektur für den Fall, dass sie entlang des unvorhergesehenen Pfads verzweigt.


13

Lesen Sie den Artikel Bessere Leistung durch Eliminierung von Zweigen zur Zellleistung. Ein weiterer Spaß ist dieser Beitrag über verzweigungslose Auswahlen im Real Time Collision Detection Blog.

Zusätzlich zu den hervorragenden Antworten, die bereits als Antwort auf diese Frage veröffentlicht wurden, möchte ich daran erinnern, dass "if" -Anweisungen zwar als teure Operationen auf niedriger Ebene angesehen werden, jedoch versucht wird, verzweigungsfreie Programmiertechniken in einer Umgebung auf höherer Ebene zu verwenden B. eine Skriptsprache oder eine Geschäftslogikschicht (unabhängig von der Sprache), kann lächerlich unangemessen sein.

In den allermeisten Fällen sollten Programme zuerst aus Gründen der Klarheit geschrieben und dann für die Leistung optimiert werden. Es gibt zahlreiche Problembereiche, in denen die Leistung von größter Bedeutung ist. Die einfache Tatsache ist jedoch, dass die meisten Entwickler keine Module für die Verwendung tief im Kern einer Rendering-Engine oder einer wochenlangen Hochleistungssimulation der Fluiddynamik schreiben. Wenn die oberste Priorität für Ihre Lösung darin besteht, "nur zu funktionieren", sollten Sie sich als letztes überlegen, ob Sie den Aufwand für eine bedingte Anweisung in Ihrem Code sparen können oder nicht.


Tatsächlich! Man könnte auch hinzufügen, dass beim Codieren in einer Sprache, die Aufrufe fördert (im Grunde alles andere als Assembler oder C ohne stdlib), Pipeline-Interferenzen durch normale Programmiertechniken alle Fragen zur bedingten Verzweigung überwältigen.
Ross Patterson

10

ifan sich ist nicht langsam. Langsamkeit ist immer relativ Ich wette für mein Leben, dass Sie noch nie den "Overhead" einer if-Aussage gespürt haben. Wenn Sie einen Hochleistungscode erstellen möchten, sollten Sie Verzweigungen trotzdem vermeiden. Was iflangsam macht, ist, dass der Prozessor Code von nachher auf der ifGrundlage einer Heuristik und so weiter vorlädt. Außerdem wird verhindert, dass Pipelines Code direkt nach dem ifVerzweigungsbefehl im Maschinencode ausführen, da der Prozessor noch nicht weiß, welchen Pfad er einschlagen wird (in einem Pipeline-Prozessor werden mehrere Befehle verschachtelt und ausgeführt). Der ausgeführte Code muss möglicherweise in umgekehrter Reihenfolge ausgeführt werden (wenn der andere Zweig verwendet wurde. Er wird aufgerufen branch misprediction) oder noopan diesen Stellen ausgefüllt, damit dies nicht geschieht.

Wenn ifböse ist, dann switchist das Böse auch, und &&, ||auch. Mach dir keine Sorgen.


7

Auf der niedrigstmöglichen Ebene ifbesteht aus (nach Berechnung aller app-spezifischen Voraussetzungen für bestimmte if):

  • einige Testanweisungen
  • Springen Sie zu einer Stelle im Code, wenn der Test erfolgreich ist. Fahren Sie andernfalls fort.

Damit verbundene Kosten:

  • Ein Vergleich auf niedrigem Niveau - normalerweise 1 CPU-Betrieb, super billig
  • potenzieller Sprung - was teuer sein kann

Reson, warum Sprünge teuer sind:

  • Sie können zu beliebigem Code springen, der sich irgendwo im Speicher befindet, wenn sich herausstellt, dass er nicht von der CPU zwischengespeichert wird - wir haben ein Problem, weil wir auf den Hauptspeicher zugreifen müssen, der langsamer ist
  • Moderne CPUs führen eine Verzweigungsprädition durch. Sie versuchen zu erraten, ob dies erfolgreich sein wird oder nicht, und führen Code in der Pipeline aus, um die Dinge zu beschleunigen. Wenn die Vorhersage fehlschlägt, müssen alle von der Pipeline im Voraus durchgeführten Berechnungen ungültig gemacht werden. Das ist auch eine teure Operation

Um es zusammenzufassen:

  • Wenn es teuer sein kann, wenn Sie sich wirklich, wirklich, um die Leistung kümmern.
  • Sie sollten sich genau dann darum kümmern, wenn Sie einen Echtzeit-Raytracer oder eine biologische Simulation oder ähnliches schreiben. In den meisten Teilen der realen Welt gibt es keinen Grund, sich darum zu kümmern.

Bringen Sie dies auf die nächste Ebene: Was ist mit verschachtelten und / oder zusammengesetzten if-Anweisungen? Die Kosten können schnell spürbar werden, wenn jemand viele solcher if-Aussagen schreibt. Und da für die meisten Entwickler Aussagen, die wie eine so grundlegende Operation erscheinen, die Vermeidung der verschlungenen bedingten Verzweigung häufig zu einem stilistischen Problem wird. Stilistische Anliegen sind immer noch wichtig, aber oft in der Hitze des Augenblicks können sie das erste Anliegen sein, das ignoriert wird.
Jaydel

7

Moderne Prozessoren haben lange Ausführungspipelines, was bedeutet, dass mehrere Befehle gleichzeitig in verschiedenen Stufen ausgeführt werden. Sie kennen möglicherweise nicht immer das Ergebnis einer Anweisung, wenn die nächste ausgeführt wird. Wenn sie auf einen bedingten Sprung stoßen (wenn), müssen sie manchmal warten, bis die Pipeline leer ist, bevor sie wissen, in welche Richtung der Befehlszeiger gehen soll.

Ich betrachte es als einen langen Güterzug. Es kann viel Fracht schnell in einer geraden Linie transportieren, aber es kurvt schlecht.

Pentium 4 (Prescott) hatte eine bekannt lange Pipeline mit 31 Stufen.

Mehr auf Wikipedia


3
+1 für die Metapher des Güterzuges - Ich werde mich daran erinnern, dass ich beim nächsten Mal die Prozessor-Pipelines erklären muss.
Daniel Pryden

6

Vielleicht beendet die Verzweigung das Vorabrufen von CPU-Befehlen?


Bei meiner ... "Recherche" habe ich etwas über Sprungtabellen und Verzweigungen für die switch-Anweisungen gelernt, aber nichts über die if-Anweisungen. Könnten Sie das etwas näher erläutern?
Pek

IIRC, die CPU ruft normalerweise Anweisungen entlang eines einzelnen wahrscheinlichen Ausführungspfads vor, aber eine 'if'-Anweisung, die eine Verzweigung vom vorhergesagten Ausführungspfad verursacht, macht die vorab abgerufenen Anweisungen ungültig und das Preteching muss neu gestartet werden.
activout.se

Jeder anständige Prozessor sollte über Verzweigungsvorhersagefunktionen verfügen, die versuchen, zu erraten, ob eine Verzweigung genommen wird oder nicht, und Vorabrufanweisungen basierend auf der Vorhersage (was im Allgemeinen recht gut ist). GCC verfügt sogar über C-Erweiterungen, mit denen ein Programmierer Hinweise für Verzweigungsvorhersagen bereitstellen kann.
Mipadi

2
Darüber hinaus sieht die CPU in der Regel voraus, um anstehende Anweisungen frühzeitig auszuführen (nicht nur vorab abzurufen), und der Compiler versucht, Anweisungen neu zu ordnen. Dies wird in allen Zweigen gefährlich, sodass Sie die Befehlsplanung mit zu vielen Zweigen wirklich beenden können. Was der Leistung schadet.
Jalf

6

Beachten Sie auch, dass innerhalb einer Schleife nicht unbedingt sehr teuer ist.

Die moderne CPU geht beim ersten Besuch einer if-Anweisung davon aus, dass der "if-body" genommen werden soll (oder anders gesagt: Sie geht auch davon aus, dass ein Schleifenkörper mehrfach genommen wird) (*). Bei zweiten und weiteren Besuchen kann es (die CPU) möglicherweise in die Zweigverlaufstabelle schauen und sehen, wie der Zustand das letzte Mal war (war es wahr? War es falsch?). Wenn es das letzte Mal falsch war, wird die spekulative Ausführung zum "else" des if oder über die Schleife hinaus fortgesetzt.

(*) Die Regel lautet tatsächlich " Vorwärtszweig nicht genommen, Rückwärtszweig genommen ". In einer if-Anweisung gibt es nur einen [Vorwärts-] Sprung (bis zum Punkt nach dem if-body ), wenn die Bedingung als falsch ausgewertet wird (denken Sie daran: Die CPU geht sowieso davon aus, keinen Zweig / Sprung zu machen), sondern in einer Schleife Es gibt möglicherweise einen Vorwärtszweig zur Position nach der Schleife (nicht zu nehmen) und einen Rückwärtszweig bei Wiederholung (zu nehmen).

Dies ist auch einer der Gründe, warum ein Aufruf einer virtuellen Funktion oder eines Funktionszeigeraufrufs nicht so schlimm ist, wie viele annehmen ( http://phresnel.org/blog/ ).


5

Wie von vielen betont, können bedingte Verzweigungen auf einem modernen Computer sehr langsam sein.

Abgesehen davon gibt es eine ganze Reihe von bedingten Zweigen, in denen if-Anweisungen nicht leben. Sie können nicht immer sagen, was der Compiler einfallen wird, und sich Sorgen darüber zu machen, wie lange grundlegende Anweisungen dauern werden, ist praktisch immer das Falsche machen. (Wenn Sie feststellen können, was der Compiler zuverlässig generiert, verfügen Sie möglicherweise nicht über einen guten optimierenden Compiler.)


4

Das einzige, was ich mir vorstellen kann, ist die Tatsache, dass eine ifAussage im Allgemeinen zu einer Verzweigung führen kann. Abhängig von den Besonderheiten der Prozessorarchitektur können Verzweigungen zu Pipeline-Stillständen oder anderen nicht optimalen Situationen führen.

Dies ist jedoch äußerst situationsspezifisch - die meisten modernen Prozessoren verfügen über Verzweigungsvorhersagefunktionen, mit denen versucht wird, die negativen Auswirkungen der Verzweigung zu minimieren. Ein anderes Beispiel wäre, wie die ARM-Architektur (und wahrscheinlich auch andere) mit bedingter Logik umgehen kann - der ARM verfügt über eine bedingte Ausführung auf Befehlsebene, sodass eine einfache bedingte Logik zu keiner Verzweigung führt - die Befehle werden einfach als NOPs ausgeführt, wenn die Bedingungen nicht erfüllt sind.

Alles, was gesagt wurde - machen Sie Ihre Logik richtig, bevor Sie sich um dieses Zeug kümmern. Falscher Code ist so unoptimiert wie möglich.


Ich habe gehört, dass die bedingten Anweisungen von ARM ILP hemmen, sodass sie das Problem möglicherweise nur herumschubsen.
JD

3

CPUs sind tief verwurzelt. Jeder Verzweigungsbefehl (if / for / while / switch / etc) bedeutet, dass die CPU nicht wirklich weiß, welcher Befehl als nächstes geladen und ausgeführt werden soll.

Die CPU bleibt entweder stehen, während sie darauf wartet, was zu tun ist, oder die CPU nimmt eine Vermutung an. Im Fall einer älteren CPU oder wenn die Vermutung falsch ist, müssen Sie einen Pipeline-Stillstand erleiden, während die richtige Anweisung geladen wird. Abhängig von der CPU kann dies bis zu 10-20 Anweisungen wert sein.

Moderne CPUs versuchen dies zu vermeiden, indem sie eine gute Verzweigungsvorhersage durchführen, mehrere Pfade gleichzeitig ausführen und nur den tatsächlichen beibehalten. Das hilft sehr, kann aber nur so weit gehen.

Viel Glück in der Klasse.

Wenn Sie sich im wirklichen Leben darüber Gedanken machen müssen, machen Sie wahrscheinlich Betriebssystemdesign, Echtzeitgrafiken, wissenschaftliches Rechnen oder etwas ähnlich CPU-gebundenes. Profil vor der Sorge.


2

Schreiben Sie Ihre Programme auf die klarste, einfachste und sauberste Weise, die offensichtlich nicht ineffizient ist. Das nutzt die teuerste Ressource, die Sie am besten nutzen. Sei es das Schreiben oder spätere Debuggen (erfordert Verständnis) des Programms. Wenn die Leistung nicht ausreicht, messen Siewo die Engpässe sind und wie sie gemindert werden können. Nur in äußerst seltenen Fällen müssen Sie sich dabei um einzelne (Quell-) Anweisungen kümmern. Bei der Leistung geht es darum, die richtigen Algorithmen und Datenstrukturen in der ersten Zeile auszuwählen, sorgfältig zu programmieren und eine ausreichend schnelle Maschine zu erhalten. Wenn Sie einen guten Compiler verwenden, werden Sie überrascht sein, wie viel Code ein moderner Compiler umstrukturiert. Die Umstrukturierung des Codes für die Leistung ist eine Art letzter Ausweg, der Code wird komplexer (daher fehlerhafter), schwieriger zu ändern und damit rundum teurer.



0

Ich hatte diesen Streit einmal mit einem Freund von mir. Er verwendete einen sehr naiven Kreisalgorithmus, behauptete jedoch, er sei schneller als meiner (die Art, die nur 1/8 des Kreises berechnet), weil meiner if verwendete. Am Ende wurde die if-Anweisung durch sqrt ersetzt und irgendwie war das schneller. Vielleicht, weil die FPU sqrt eingebaut hat?


-1

Das teuerste in Bezug auf die ALU-Nutzung? Es verwendet CPU-Register zum Speichern der zu vergleichenden Werte und benötigt Zeit, um die Werte jedes Mal abzurufen und zu vergleichen, wenn die if-Anweisung ausgeführt wird.

Daher besteht eine Optimierung darin, einen Vergleich durchzuführen und das Ergebnis als Variable zu speichern, bevor die Schleife ausgeführt wird.

Ich versuche nur, deine fehlenden Wörter zu 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.