If vs. Switch Speed

112

Switch-Anweisungen sind aufgrund von Compiler-Optimierungen in der Regel schneller als gleichwertige if-else-if-Anweisungen (wie z. B. in diesem Artikel beschrieben ).

Wie funktioniert diese Optimierung tatsächlich? Hat jemand eine gute Erklärung?

Dirk Vollmar
quelle
Eine mögliche gute Antwort: dotnetperls.com/if-switch-performance
Babak

Antworten:

185

Der Compiler kann gegebenenfalls Sprungtabellen erstellen. Wenn Sie beispielsweise den Reflektor verwenden, um den erzeugten Code zu betrachten, werden Sie feststellen, dass der Compiler bei großen Schaltern von Zeichenfolgen tatsächlich Code generiert, der eine Hash-Tabelle verwendet, um diese zu versenden. Die Hash-Tabelle verwendet die Zeichenfolgen als Schlüssel und delegiert sie caseals Werte an die Codes.

Dies hat eine asymptotisch bessere Laufzeit als viele verkettete ifTests und ist sogar für relativ wenige Saiten schneller.

Konrad Rudolph
quelle
6
Gute Antwort, interessant über die Hash-Tabelle.
BobbyShaftoe
4
In einigen Fällen werden sie auch in Baumvergleiche konvertiert. Die Argumentation ist etwas komplex, läuft aber im Grunde genommen auf die Tabellenindirektion hinaus, die moderne CPU-Sprungzielpuffer neutralisiert und so den Verzweigungsprädiktor auslöscht. Ich erinnere mich vage an ein Papier auf einer GCC-Konferenz über Codegen für Schalter.
olliej
Das heißt: Schalter (a) Fall "x": Fall "y": Fall "z": // etwas kaputt; } ist schneller als: if (a == "x" || a == "b" || a == "c") // stimmt etwas?
Yazanpro
hier haben wir keine verschachtelten wenn sonst nur ODER also was denkst du?
Yazanpro
@yazanpro Auf alten Compilern möglicherweise ja (aber beachten Sie, dass die Anzahl der Fälle so gering ist, dass es möglicherweise keinen Unterschied macht!). Moderne Compiler führen jedoch viel mehr Code-Analysen durch. Infolgedessen stellen sie möglicherweise fest, dass diese beiden Codefragmente gleichwertig sind, und wenden dieselben Optimierungen an. Aber das ist reine Spekulation meinerseits, ich weiß nicht, ob ein Compiler das tatsächlich tut.
Konrad Rudolph
15

Dies ist eine leichte Vereinfachung, wie dies normalerweise bei jedem modernen Compiler der if..else if ..Fall ist , der auf eine Sequenz stößt , die von einer Person trivial in eine switch-Anweisung konvertiert werden könnte. Aber nur um zusätzlichen Spaß zu machen, ist der Compiler nicht durch die Syntax eingeschränkt, sondern kann intern "switch" -ähnliche Anweisungen generieren, die eine Mischung aus Bereichen, einzelnen Zielen usw. enthalten - und dies kann (und tut) dies sowohl für switch als auch für if. .else Aussagen.

Wie auch immer, eine Erweiterung von Konrads Antwort ist, dass der Compiler möglicherweise eine Sprungtabelle generiert, aber das ist nicht unbedingt garantiert (noch wünschenswert). Aus einer Vielzahl von Gründen tun Sprungtabellen den Zweigprädiktoren auf modernen Prozessoren schlechte Dinge, und die Tabellen selbst tun schlechte Dinge, um das Verhalten zwischenzuspeichern, z.

switch(a) { case 0: ...; break; case 1: ...; break; }

Wenn ein Compiler tatsächlich eine Sprungtabelle dafür generieren würde, wäre es wahrscheinlich langsamer als der alternative if..else if..Stilcode, da die Sprungtabelle die Verzweigungsvorhersage besiegt.

olliej
quelle
4

Die No-Match-Statistiken sind möglicherweise nicht gut.

Wenn Sie die Quelle tatsächlich herunterladen, ist bekannt, dass die Werte für keine Übereinstimmung 21 sind, sowohl im if- als auch im switch-Fall. Ein Compiler sollte in der Lage sein, zu abstrahieren und zu wissen, welche Anweisung jederzeit ausgeführt werden sollte, und eine CPU sollte in der Lage sein, Vorhersagen richtig zu verzweigen.

Der interessantere Fall ist, wenn meiner Meinung nach nicht jeder Fall bricht, aber das war möglicherweise nicht der Umfang des Experiments.

Calyth
quelle
4

Switch- / Case-Anweisungen sind in der Regel schneller und 1-stufig, aber wenn Sie mit 2 oder mehr beginnen, dauern Switch- / Case-Anweisungen 2-3 Mal so lange wie verschachtelte if / else-Anweisungen.

Dieser Artikel enthält einige Geschwindigkeitsvergleiche, in denen die Geschwindigkeitsunterschiede hervorgehoben werden, wenn solche Anweisungen verschachtelt sind.

Beispiel: Code gemäß den Tests lautet wie folgt:

if (x % 3 == 0)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 1)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 2)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;

in der Hälfte der Zeit beendet, die die entsprechende switch / case-Anweisung zum Ausführen benötigt hat:

switch (x % 3)
    {
        case 0:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
        case 1:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    case 2:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    default:
        switch (y % 3)
        {
            case 0: total += 3;
                break;
            case 1: total += 2;
                break;
            case 2: total += 1;
                break;
            default: total += 0;
                break;
        }
        break;
    }

Ja, es ist ein rudimentäres Beispiel, aber es veranschaulicht den Punkt.

Eine Schlussfolgerung könnte also sein, switch / case für einfache Typen zu verwenden, die nur eine Ebene tief sind, aber für komplexere Vergleiche und mehrere verschachtelte Ebenen die klassischen if / else-Konstrukte verwenden?


quelle
-1: 1. Der Artikel ignorierte die Verzweigungsvorhersage vollständig, 2. die Algorithmen sind nicht genau gleich (der einzelne if-else-Link auf dem Link ist bereits optimierter codiert) und 3. die gefundenen Unterschiede sind so gering, dass nichts entschuldigt die Verwendung von korrektem, sauberem Code (ungefähr 4 ns in 10.000.000 Aufrufen zwischen Switch und demselben if-else-Konstrukt)
Trojaner
Dieses Beispiel wird nicht optimiert, da der Switch-Block nur wenige Fälle aufweist. Normalerweise wird nach 5-6 Elementen eine Sprungtabelle generiert.
Antiduh
0

Der einzige Vorteil des If-Over-Falls besteht darin, dass die Häufigkeit des Auftretens des ersten Falls merklich zunimmt.

Ich bin mir nicht sicher, wo genau der Schwellenwert liegt, aber ich verwende die Groß- und Kleinschreibung, es sei denn, der erste "fast immer" besteht den ersten Test.

Ralph
quelle