web-dev-qa-db-ger.com

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

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

117
Anth0

Das sind Mikrooptimierungen und vorzeitige Optimierungen, die böse sind. Sorgen Sie sich eher um die Lesbarkeit und Wartbarkeit des betreffenden Codes. Wenn mehr als zwei if/else - Blöcke zusammengeklebt sind oder die Größe unvorhersehbar ist, können Sie eine switch -Anweisung in Betracht ziehen.

Alternativ können Sie auch Polymorphismus. Zuerst erstelle ein Interface:

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

Und besorgen Sie sich alle Implementierungen in einigen Map. Sie können dies entweder statisch oder dynamisch tun:

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

Ersetzen Sie abschließend if/else Oder switch durch so etwas (lassen Sie triviale Prüfungen wie Nullzeiger beiseite):

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

Es ist könnte kleiner als if/else Oder switch, aber der Code ist zumindest weitaus besser wartbar.

Wenn Sie über Webanwendungen sprechen, können Sie HttpServletRequest#getPathInfo() als Aktionstaste verwenden (schreiben Sie eventuell etwas mehr Code, um den letzten Teil von pathinfo in einer Schleife bis zu einer Aktion aufzuteilen gefunden). Hier finden Sie ähnliche Antworten:

Wenn Sie sich über die Leistung von Java EE-Webapplikationen im Allgemeinen Gedanken machen, können Sie diesen Artikel auch nützlich finden. Es gibt andere Bereiche, die eine viel mehr Leistungsgewinn als nur (Mikro-) Optimierung des rohen Java Code.

104
BalusC

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

Aber es ist wahr, dass der Java VM) spezielle Bytecodes hat, die für switch () verwendet werden könnten.

Siehe WM-Spezifikation ( Suchschalter und Tischschalter )

Wenn der Code Teil des Leistungs-CPU-Diagramms ist, kann dies zu Leistungssteigerungen führen.

117
Waverick

Es ist äußerst unwahrscheinlich, dass ein if/else oder ein Switch die Ursache Ihrer 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!

Dennoch ist es möglich, mit den Java Compiler-Optimierungen) über die relative Leistung von switch vs. Im Allgemeinen können Sie eine switch-Anweisung wie folgt anzeigen:

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

woher c_0, c_1, ..., und c_N sind ganze Zahlen, die Ziele der switch-Anweisung sind, und <condition> muss in einen ganzzahligen Ausdruck aufgelöst werden.

  • Wenn diese Menge "dicht" ist - das heißt, (max (cich) + 1 - min (cich))/n> α, wobei 0 <k <α <1, wobei k größer als ein Erfahrungswert ist, kann eine Sprungtabelle erzeugt werden, die hocheffizient 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 Folge von if/else-Anweisungen. Die genauen Werte von α und β hängen von einer Reihe von Faktoren ab und werden vom Code-Optimierungsmodul des Compilers bestimmt.

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

49
John Feminella

Schalter benutzen!

Ich hasse es, if-else-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

10
Bitterblue

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

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

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

Cliff gibt ein Beispiel (Ende auf Folie 30), das zeigt, dass selbst bei einer CPU, die das Umbenennen von Registern, die Verzweigungsvorhersage und die spekulative Ausführung durchführt, nur sieben Operationen in vier Taktzyklen gestartet werden können, bevor sie aufgrund von zwei Cachefehlern blockiert werden müssen 300 Taktzyklen, um zurückzukehren.

Um Ihr Programm zu beschleunigen, sollten Sie sich also nicht mit dieser Art kleinerer Probleme befassen, sondern mit größeren Problemen, z. B. der Frage, ob Sie unnötige Datenformatkonvertierungen vornehmen, z. B. die Konvertierung von "SOAP → XML → DOM → SQL →… msgstr "welcher" alle Daten durch den Cache leitet ".

8
Jim Ferrans

Ich erinnere mich, dass ich gelesen habe, dass es in Java bytecode) 2 Arten von Switch-Anweisungen gibt. (Ich glaube, es war in 'Java Performance Tuning'. Eine ist eine sehr schnelle Implementierung, die die Integer-Werte der switch-Anweisung verwendet, um sie zu kennen Der Offset des auszuführenden Codes. Dies würde erfordern, dass alle Ganzzahlen aufeinander folgen und sich in einem genau definierten Bereich befinden. Ich vermute, dass die Verwendung aller Werte einer Aufzählung auch in diese Kategorie fallen würde.

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

8
malaverdiere

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

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
 */ 
4

Für die meisten switch und die meisten if-then-else Blocks, ich kann mir nicht vorstellen, dass es irgendwelche nennenswerten oder signifikanten Leistungsprobleme gibt.

Aber hier ist die Sache: Wenn Sie einen switch -Block verwenden, deutet dies darauf hin, dass Sie einen Wert einschalten, der aus einer Reihe von Konstanten stammt, die zur Kompilierungszeit bekannt sind. In diesem Fall sollten Sie überhaupt keine switch -Anweisungen verwenden, wenn Sie ein enum mit konstantenspezifischen Methoden verwenden können.

Im Vergleich zu einer switch -Anweisung bietet eine Aufzählung eine bessere Typensicherheit und Code, der einfacher zu warten ist. Aufzählungen können so gestaltet werden, dass der 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, einem case -Block einen neuen switch -Block hinzuzufügen, manchmal nur zur Laufzeit abgefangen werden, wenn Sie das Glück haben, Ihren Block so eingerichtet zu haben, dass er eine Ausnahme auslöst.

Die Leistung zwischen einer switch und einer enum konstanten-spezifischen Methode sollte sich nicht wesentlich unterscheiden. Letztere ist jedoch besser lesbar, sicherer und einfacher zu warten.

2
scottb