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
if
Aussage 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?
Antworten:
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
if
Aussagen und Kontrolltests vonfor
undwhile
Schleifen angezeigt. Unbedingte Verzweigungen werden in Endlosschleifen, Funktionsaufrufen, Funktionsrückgabenbreak
undcontinue
Anweisungen, der berüchtigtengoto
Anweisung 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 beinhaltet
if
Anweisungen, Schleifen aller Art, regelmäßige Funktionsaufrufe und vieles mehr. Berechnete Verzweigungen berechnet das Ziel der Verzweigung zur Laufzeit. Dies umfasstswitch
(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()
undmax()
ohne Verzweigung.quelle
"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.quelle
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.
quelle
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.
quelle
if
an 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. Wasif
langsam macht, ist, dass der Prozessor Code von nachher auf derif
Grundlage einer Heuristik und so weiter vorlädt. Außerdem wird verhindert, dass Pipelines Code direkt nach demif
Verzweigungsbefehl 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 aufgerufenbranch misprediction
) odernoop
an diesen Stellen ausgefüllt, damit dies nicht geschieht.Wenn
if
böse ist, dannswitch
ist das Böse auch, und&&
,||
auch. Mach dir keine Sorgen.quelle
Auf der niedrigstmöglichen Ebene
if
besteht aus (nach Berechnung aller app-spezifischen Voraussetzungen für bestimmteif
):Damit verbundene Kosten:
Reson, warum Sprünge teuer sind:
Um es zusammenzufassen:
quelle
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
quelle
Vielleicht beendet die Verzweigung das Vorabrufen von CPU-Befehlen?
quelle
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/ ).
quelle
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.)
quelle
Das einzige, was ich mir vorstellen kann, ist die Tatsache, dass eine
if
Aussage 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.
quelle
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.
quelle
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.
quelle
Einige CPUs (wie X86) bieten eine Verzweigungsvorhersage auf Programmierebene, um eine solche Latenz für die Verzweigungsvorhersage zu vermeiden.
Einige Compiler (wie GCC) stellen diese als Erweiterung für übergeordnete Programmiersprachen (wie C / C ++) zur Verfügung.
Verweisen Sie auf wahrscheinliche () / unwahrscheinliche () Makros im Linux-Kernel - wie funktionieren sie? Was ist ihr Vorteil? .
quelle
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?
quelle
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.
quelle