Was ist der relative Leistungsunterschied zwischen if / else und switch-Anweisung in Java?

122

Ich mache mir Sorgen um die Leistung meiner Webanwendung und frage mich, welche der "if / else" - oder switch-Anweisungen hinsichtlich der Leistung besser ist.

Anth0
quelle
6
Haben Sie Grund zu der Annahme, dass für beide Konstrukte nicht derselbe Bytecode generiert wird?
Pascal Cuoq
2
@Pascal: Möglicherweise wird eine Optimierung durchgeführt, indem Tabellensuchen anstelle einer Liste von ifusw. verwendet werden.
jldupont
18
"Vorzeitige Optimierung ist die Wurzel allen Übels" - Donald Knuth
fehlender Faktor
104
Während dies definitiv eine vorzeitige Optimierung ist, "ist die sinnlose Einhaltung eines Zitats, das schlecht aus dem Zusammenhang gerissen wurde, der Grund, warum wir einen High-End-Multi-Core-Computer benötigen, um heute eine einigermaßen reaktionsschnelle Benutzeroberfläche anzuzeigen" - Me.
Lawrence Dol
2
Knuth hat einen präzisen Verstand. Bitte beachten Sie das Qualifikationsmerkmal "verfrüht". Optimierung ist ein durchaus berechtigtes Anliegen. Ein Server ist jedoch an E / A gebunden, und die Engpässe bei Netzwerk- und Festplatten-E / A sind um Größenordnungen wichtiger als alles andere, was Sie auf Ihrem Server tun.
Alphazero

Antworten:

108

Das sind Mikrooptimierung und vorzeitige Optimierung, die böse sind. Sorgen Sie sich lieber um die Lesbarkeit und Wartbarkeit des betreffenden Codes. Wenn mehr als zwei if/elseBlöcke zusammengeklebt sind oder ihre Größe nicht vorhersehbar ist, können Sie eine switchAussage in Betracht ziehen .

Alternativ können Sie auch Polymorphismus greifen . Erstellen Sie zuerst eine Schnittstelle:

public interface Action { 
    void execute(String input);
}

Und in einigen Fällen alle Implementierungen in den Griff bekommen Map. Sie können dies entweder statisch oder dynamisch tun:

Map<String, Action> actions = new HashMap<String, Action>();

Ersetzen Sie schließlich das if/elseoder switchdurch etwas Ähnliches (lassen Sie triviale Überprüfungen wie Nullzeiger beiseite):

actions.get(name).execute(input);

Es ist möglicherweise mikroslower als if/elseoder switch, aber der Code ist zumindest weitaus besser zu warten.

Wenn Sie über Webanwendungen sprechen, können Sie diese HttpServletRequest#getPathInfo()als Aktionsschlüssel verwenden (schreiben Sie eventuell etwas mehr Code, um den letzten Teil von pathinfo in einer Schleife aufzuteilen, bis eine Aktion gefunden wird). Ähnliche Antworten finden Sie hier:

Wenn Sie sich allgemein Gedanken über die Leistung der Java EE-Webanwendung machen, ist dieser Artikel möglicherweise ebenfalls hilfreich. Es gibt andere Bereiche, die einen viel größeren Leistungsgewinn bieten als nur die (Mikro-) Optimierung des Java-Rohcodes.

BalusC
quelle
1
oder betrachten Sie stattdessen Polymorphismus
jk.
Dies ist in der Tat empfehlenswerter, wenn die Anzahl der if / else-Blöcke "unvorhersehbar" ist.
BalusC
73
Ich bin nicht so schnell dabei, jede frühe Optimierung als "böse" abzutun. Zu aggressiv zu sein ist töricht, aber angesichts von Konstrukten mit vergleichbarer Lesbarkeit ist es eine angemessene Entscheidung, eines zu wählen, von dem bekannt ist, dass es eine bessere Leistung erbringt.
Brian Knoblauch
8
Die HashMap-Suchversion kann im Vergleich zu einer Tableswitsch-Anweisung leicht zehnmal langsamer sein. Ich würde das nicht "Mikroslower" nennen!
x4u
7
Ich bin daran interessiert, das Innenleben von Java im allgemeinen Fall mit switch-Anweisungen zu kennen. Es interessiert mich nicht, ob jemand der Meinung ist, dass dies mit einer Überpriorisierung der frühen Optimierung zusammenhängt oder nicht. Davon abgesehen habe ich absolut keine Ahnung, warum diese Antwort so positiv bewertet wird und warum es die akzeptierte Antwort ist ... dies trägt nichts zur Beantwortung der ursprünglichen Frage bei.
Searchengine27
125

Ich stimme voll und ganz der Meinung zu, dass eine vorzeitige Optimierung vermieden werden sollte.

Es stimmt jedoch, dass die Java-VM über spezielle Bytecodes verfügt, die für switch () verwendet werden können.

Siehe WM- Spezifikation ( Lookupswitch und Tableswitch )

Es kann also zu Leistungssteigerungen kommen, wenn der Code Teil des Leistungs-CPU-Diagramms ist.

Waverick
quelle
60
Ich frage mich, warum dieser Kommentar nicht höher bewertet wird: Er ist der informativste von allen. Ich meine: Wir alle wissen bereits, dass vorzeitige Optimierung schlecht ist und so, dass wir das nicht zum 1000. Mal erklären müssen.
Folkert van Heusden
5
+1 Ab stackoverflow.com/a/15621602/89818 scheinen die Leistungssteigerungen wirklich vorhanden zu sein, und Sie sollten einen Vorteil sehen, wenn Sie mehr als 18 Fälle verwenden.
Caw
52

Es ist äußerst unwahrscheinlich, dass ein if / else oder ein Switch die Ursache für Ihre Leistungsprobleme sein wird. Wenn Sie Leistungsprobleme haben, sollten Sie zuerst eine Leistungsprofilanalyse durchführen, um festzustellen, wo sich die langsamen Stellen befinden. Vorzeitige Optimierung ist die Wurzel allen Übels!

Trotzdem ist es möglich, mit den Java-Compiler-Optimierungen über die relative Leistung von Switch im Vergleich zu if / else zu sprechen. Beachten Sie zunächst, dass switch-Anweisungen in Java mit einer sehr begrenzten Domäne arbeiten - Ganzzahlen. Im Allgemeinen können Sie eine switch-Anweisung wie folgt anzeigen:

switch (<condition>) {
   case c_0: ...
   case c_1: ...
   ...
   case c_n: ...
   default: ...
}

wobei c_0, c_1, ... und c_Nsind ganze Zahlen , die Ziele der switch - Anweisung sind, und <condition>müssen auf eine ganze Zahl Ausdruck lösen.

  • Wenn diese Menge "dicht" ist, dh (max (c i ) + 1 - min (c i )) / n> α, wobei 0 <k <α <1, wobei kgrößer als ein empirischer Wert ist, a Es kann eine Sprungtabelle erzeugt werden, die sehr effizient ist.

  • Wenn diese Menge nicht sehr dicht ist, aber n> = β, kann ein binärer Suchbaum das Ziel in O (2 * log (n)) finden, was ebenfalls noch effizient ist.

In allen anderen Fällen ist eine switch-Anweisung genauso effizient wie die entsprechende Reihe von if / else-Anweisungen. Die genauen Werte von α und β hängen von einer Reihe von Faktoren ab und werden vom Codeoptimierungsmodul des Compilers bestimmt.

Schließlich <condition>ist eine switch-Anweisung natürlich völlig nutzlos , wenn die Domäne von nicht die ganzen Zahlen sind.

John Feminella
quelle
+1. Es besteht eine gute Chance, dass die für Netzwerk-E / A aufgewendete Zeit dieses spezielle Problem leicht in den Schatten stellt.
Adam Paynter
3
Es ist zu beachten, dass Schalter nicht nur mit Ints arbeiten. Aus den Java-Tutorials: "Ein Switch funktioniert mit den primitiven Datentypen Byte, Short, Char und Int. Er funktioniert auch mit aufgezählten Typen (siehe Aufzählungstypen), der String-Klasse und einigen speziellen Klassen, die bestimmte primitive Typen umschließen : Zeichen, Byte, Kurz und Ganzzahl (siehe Zahlen und Zeichenfolgen). " Die Unterstützung für String ist eine neuere Ergänzung. hinzugefügt in Java 7. docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html
atraudes
1
@jhonFeminella Könnten Sie bitte die BIG O-Begriffseffekte für Java7 String in Swtich mit String in if / else if .. vergleichen?
Kanagavelu Sugumar
Genauer gesagt, javac 8 gibt der Zeitkomplexität ein Gewicht von 3 gegenüber der Raumkomplexität: stackoverflow.com/a/31032054/895245
Ciro Santilli 24 冠状 病 六四 事件 法轮功
11

Schalter benutzen!

Ich hasse es, wenn-sonst-Blöcke zu pflegen! Habe einen Test:

public class SpeedTestSwitch
{
    private static void do1(int loop)
    {
        int temp = 0;
        for (; loop > 0; --loop)
        {
            int r = (int) (Math.random() * 10);
            switch (r)
            {
                case 0:
                    temp = 9;
                    break;
                case 1:
                    temp = 8;
                    break;
                case 2:
                    temp = 7;
                    break;
                case 3:
                    temp = 6;
                    break;
                case 4:
                    temp = 5;
                    break;
                case 5:
                    temp = 4;
                    break;
                case 6:
                    temp = 3;
                    break;
                case 7:
                    temp = 2;
                    break;
                case 8:
                    temp = 1;
                    break;
                case 9:
                    temp = 0;
                    break;
            }
        }
        System.out.println("ignore: " + temp);
    }

    private static void do2(int loop)
    {
        int temp = 0;
        for (; loop > 0; --loop)
        {
            int r = (int) (Math.random() * 10);
            if (r == 0)
                temp = 9;
            else
                if (r == 1)
                    temp = 8;
                else
                    if (r == 2)
                        temp = 7;
                    else
                        if (r == 3)
                            temp = 6;
                        else
                            if (r == 4)
                                temp = 5;
                            else
                                if (r == 5)
                                    temp = 4;
                                else
                                    if (r == 6)
                                        temp = 3;
                                    else
                                        if (r == 7)
                                            temp = 2;
                                        else
                                            if (r == 8)
                                                temp = 1;
                                            else
                                                if (r == 9)
                                                    temp = 0;
        }
        System.out.println("ignore: " + temp);
    }

    public static void main(String[] args)
    {
        long time;
        int loop = 1 * 100 * 1000 * 1000;
        System.out.println("warming up...");
        do1(loop / 100);
        do2(loop / 100);

        System.out.println("start");

        // run 1
        System.out.println("switch:");
        time = System.currentTimeMillis();
        do1(loop);
        System.out.println(" -> time needed: " + (System.currentTimeMillis() - time));

        // run 2
        System.out.println("if/else:");
        time = System.currentTimeMillis();
        do2(loop);
        System.out.println(" -> time needed: " + (System.currentTimeMillis() - time));
    }
}

Mein C # -Standardcode für das Benchmarking

Bitter blau
quelle
Könnten Sie bitte (irgendwann) etwas näher darauf eingehen, wie Sie dies bewertet haben?
DerMike
Vielen Dank für Ihr Update. Ich meine, sie unterscheiden sich um eine Größenordnung - was natürlich möglich ist. Sind Sie sicher, dass der Compiler die ES nicht einfach wegoptimiert hat switch?
DerMike
@DerMike Ich erinnere mich nicht, wie ich zu den alten Ergebnissen gekommen bin. Ich bin heute ganz anders geworden. Aber probieren Sie es selbst aus und lassen Sie mich wissen, wie es ausgeht.
Bitterblue
1
wenn ich es auf meinem Laptop laufen lasse; Schaltzeit benötigt: 3585, wenn / sonst Zeit benötigt: 3458 also wenn / sonst besser ist :) oder nicht schlechter
halil
1
Die dominierenden Kosten im Test sind die Zufallszahlengenerierung. Ich habe den Test geändert, um die Zufallszahl vor der Schleife zu generieren, und den temporären Wert verwendet, um eine Rückmeldung in r zu erhalten. Der Schalter ist dann fast doppelt so schnell wie die If-else-Kette.
Boneill
8

Ich erinnere mich, dass ich gelesen habe, dass es im Java-Bytecode zwei Arten von Switch-Anweisungen gibt. (Ich denke, es war in 'Java Performance Tuning'. Eine ist eine sehr schnelle Implementierung, die die Ganzzahlwerte der switch-Anweisung verwendet, um den Offset des auszuführenden Codes zu kennen. Dies würde erfordern, dass alle Ganzzahlen aufeinanderfolgend und in einem genau definierten Bereich sind Ich vermute, dass die Verwendung aller Werte einer Aufzählung auch in diese Kategorie fallen würde.

Ich stimme jedoch vielen anderen Postern zu ... es kann verfrüht sein, sich darüber Sorgen zu machen, es sei denn, dies ist ein sehr, sehr heißer Code.

Malaverdiere
quelle
4
+1 für den Hot-Code-Kommentar. Wenn es in Ihrer Hauptschleife ist, ist es nicht verfrüht.
KingAndrew
Ja, javac implementiert switchverschiedene Methoden, von denen einige effizienter sind als andere. Im Allgemeinen ist die Effizienz nicht schlechter als bei einer einfachen " ifLeiter", aber es gibt genügend Variationen (insbesondere beim JITC), so dass es schwierig ist, viel genauer zu sein.
Hot Licks
8

Laut Cliff Click in seinem Java One-Vortrag 2009 Ein Crashkurs in moderner Hardware :

Heutzutage wird die Leistung von Mustern des Speicherzugriffs dominiert. Cache-Fehler dominieren - Speicher ist die neue Festplatte. [Folie 65]

Sie können seine vollständigen Folien hier erhalten .

Cliff gibt ein Beispiel (Abschluss auf Folie 30), das zeigt, dass selbst wenn die CPU Registerumbenennung, Verzweigungsvorhersage und spekulative Ausführung ausführt, sie nur 7 Operationen in 4 Taktzyklen starten kann, bevor sie aufgrund von zwei Cache-Fehlern blockieren muss 300 Taktzyklen, um zurückzukehren.

Um Ihr Programm zu beschleunigen, sollten Sie sich nicht mit solchen kleinen Problemen befassen, sondern mit größeren, z. B. ob Sie unnötige Datenformatkonvertierungen vornehmen, z. B. "SOAP → XML → DOM → SQL → ...". "welches" alle Daten durch den Cache leitet ".

Jim Ferrans
quelle
4

In meinem Test ist die bessere Leistung unter Windows 7 ENUM> MAP> SWITCH> IF / ELSE IF .

import java.util.HashMap;
import java.util.Map;

public class StringsInSwitch {
public static void main(String[] args) {
    String doSomething = null;


    //METHOD_1 : SWITCH
    long start = System.currentTimeMillis();
    for (int i = 0; i < 99999999; i++) {
        String input = "Hello World" + (i & 0xF);

        switch (input) {
        case "Hello World0":
            doSomething = "Hello World0";
            break;
        case "Hello World1":
            doSomething = "Hello World0";
            break;
        case "Hello World2":
            doSomething = "Hello World0";
            break;
        case "Hello World3":
            doSomething = "Hello World0";
            break;
        case "Hello World4":
            doSomething = "Hello World0";
            break;
        case "Hello World5":
            doSomething = "Hello World0";
            break;
        case "Hello World6":
            doSomething = "Hello World0";
            break;
        case "Hello World7":
            doSomething = "Hello World0";
            break;
        case "Hello World8":
            doSomething = "Hello World0";
            break;
        case "Hello World9":
            doSomething = "Hello World0";
            break;
        case "Hello World10":
            doSomething = "Hello World0";
            break;
        case "Hello World11":
            doSomething = "Hello World0";
            break;
        case "Hello World12":
            doSomething = "Hello World0";
            break;
        case "Hello World13":
            doSomething = "Hello World0";
            break;
        case "Hello World14":
            doSomething = "Hello World0";
            break;
        case "Hello World15":
            doSomething = "Hello World0";
            break;
        }
    }

    System.out.println("Time taken for String in Switch :"+ (System.currentTimeMillis() - start));




    //METHOD_2 : IF/ELSE IF
    start = System.currentTimeMillis();

    for (int i = 0; i < 99999999; i++) {
        String input = "Hello World" + (i & 0xF);

        if(input.equals("Hello World0")){
            doSomething = "Hello World0";
        } else if(input.equals("Hello World1")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World2")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World3")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World4")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World5")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World6")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World7")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World8")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World9")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World10")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World11")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World12")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World13")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World14")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World15")){
            doSomething = "Hello World0";

        }
    }
    System.out.println("Time taken for String in if/else if :"+ (System.currentTimeMillis() - start));









    //METHOD_3 : MAP
    //Create and build Map
    Map<String, ExecutableClass> map = new HashMap<String, ExecutableClass>();
    for (int i = 0; i <= 15; i++) {
        String input = "Hello World" + (i & 0xF);
        map.put(input, new ExecutableClass(){
                            public void execute(String doSomething){
                                doSomething = "Hello World0";
                            }
                        });
    }


    //Start test map
    start = System.currentTimeMillis();
    for (int i = 0; i < 99999999; i++) {
        String input = "Hello World" + (i & 0xF);
        map.get(input).execute(doSomething);
    }
    System.out.println("Time taken for String in Map :"+ (System.currentTimeMillis() - start));






    //METHOD_4 : ENUM (This doesn't use muliple string with space.)
    start = System.currentTimeMillis();
    for (int i = 0; i < 99999999; i++) {
        String input = "HW" + (i & 0xF);
        HelloWorld.valueOf(input).execute(doSomething);
    }
    System.out.println("Time taken for String in ENUM :"+ (System.currentTimeMillis() - start));


    }

}

interface ExecutableClass
{
    public void execute(String doSomething);
}



// Enum version
enum HelloWorld {
    HW0("Hello World0"), HW1("Hello World1"), HW2("Hello World2"), HW3(
            "Hello World3"), HW4("Hello World4"), HW5("Hello World5"), HW6(
            "Hello World6"), HW7("Hello World7"), HW8("Hello World8"), HW9(
            "Hello World9"), HW10("Hello World10"), HW11("Hello World11"), HW12(
            "Hello World12"), HW13("Hello World13"), HW14("Hello World4"), HW15(
            "Hello World15");

    private String name = null;

    private HelloWorld(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void execute(String doSomething){
        doSomething = "Hello World0";
    }

    public static HelloWorld fromString(String input) {
        for (HelloWorld hw : HelloWorld.values()) {
            if (input.equals(hw.getName())) {
                return hw;
            }
        }
        return null;
    }

}





//Enum version for betterment on coding format compare to interface ExecutableClass
enum HelloWorld1 {
    HW0("Hello World0") {   
        public void execute(String doSomething){
            doSomething = "Hello World0";
        }
    }, 
    HW1("Hello World1"){    
        public void execute(String doSomething){
            doSomething = "Hello World0";
        }
    };
    private String name = null;

    private HelloWorld1(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void execute(String doSomething){
    //  super call, nothing here
    }
}


/*
 * http://stackoverflow.com/questions/338206/why-cant-i-switch-on-a-string
 * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.10
 * http://forums.xkcd.com/viewtopic.php?f=11&t=33524
 */ 
Kanagavelu Sugumar
quelle
Time taken for String in Switch :3235 Time taken for String in if/else if :3143 Time taken for String in Map :4194 Time taken for String in ENUM :2866
Halil
@halil Ich bin nicht sicher, wie dieser Code in verschiedenen Umgebungen funktioniert, aber Sie haben erwähnt, ob / elseif besser als Switch und Map ist, dass ich nicht überzeugen kann, da wenn / elseif mehr ausführen muss, ist dies nicht gleichbedeutend mit Vergleich.
Kanagavelu Sugumar
2

Für die meisten switchund die meisten if-then-elseBlöcke kann ich mir nicht vorstellen, dass es nennenswerte oder signifikante leistungsbezogene Bedenken gibt.

Aber hier ist die Sache: Wenn Sie einen switchBlock verwenden, deutet seine Verwendung darauf hin, dass Sie einen Wert aktivieren, der aus einer Reihe von Konstanten stammt, die zur Kompilierungszeit bekannt sind. In diesem Fall sollten Sie wirklich überhaupt keine switchAnweisungen verwenden, wenn Sie eine verwenden könnenenum mit konstanten spezifischen Methoden verwenden können.

Im Vergleich zu einer switchAnweisung bietet eine Aufzählung eine bessere Typensicherheit und einen Code, der einfacher zu warten ist. Aufzählungen können so gestaltet werden, dass Ihr Code nicht kompiliert wird, wenn eine Konstante zum Satz von Konstanten hinzugefügt wird, ohne eine konstantenspezifische Methode für den neuen Wert bereitzustellen. Andererseits kann das Vergessen, caseeinem switchBlock einen neuen hinzuzufügen, manchmal nur zur Laufzeit abgefangen werden, wenn Sie das Glück haben, Ihren Block so eingerichtet zu haben, dass eine Ausnahme ausgelöst wird.

Die Leistung zwischen switchund einer enumkonstantenspezifischen Methode sollte sich nicht wesentlich unterscheiden. Letztere ist jedoch besser lesbar, sicherer und einfacher zu warten.

Scottb
quelle