Qual è la differenza di prestazioni relativa di if / else rispetto all'istruzione switch in Java?


123

Preoccupato per le prestazioni della mia applicazione web, mi chiedo quale tra "if / else" o switch statement sia migliore per quanto riguarda le prestazioni?


6
Hai qualche motivo per pensare che lo stesso bytecode non venga generato per i due costrutti?
Pascal Cuoq

2
@Pascal: potrebbe essere eseguita un'ottimizzazione utilizzando ricerche di tabelle invece di un elenco di ifecc.
jldupont

18
"L'ottimizzazione prematura è la radice di tutti i mali" - Donald Knuth
missingfaktor

104
Sebbene questa sia decisamente un'ottimizzazione prematura, "L'aderenza senza cervello a una citazione presa male fuori contesto è la ragione per cui abbiamo bisogno di un computer multi-core di fascia alta solo per visualizzare una GUI ragionevolmente reattiva oggi" - Me.
Lawrence Dol

2
Knuth ha una mente precisa. Si prega di notare il qualificatore "prematuro". L'ottimizzazione è una preoccupazione perfettamente valida. Detto questo, un server è vincolato a I / O e i colli di bottiglia della rete e dell'I / O del disco sono ordini di grandezza più significativi di qualsiasi altra cosa tu abbia sul tuo server.
alphazero

Risposte:


108

Questa è micro ottimizzazione e ottimizzazione prematura, che sono il male. Preoccupati piuttosto della leggibilità e della manutenibilità del codice in questione. Se ci sono più di due if/elseblocchi incollati insieme o la sua dimensione è imprevedibile, allora potresti prendere in considerazione switchun'affermazione.

In alternativa, puoi anche prendere Polymorphism . Per prima cosa crea un'interfaccia:

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

E procurati tutte le implementazioni in alcuni Map. Puoi farlo staticamente o dinamicamente:

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

Infine sostituisci if/elseo switchcon qualcosa di simile (lasciando da parte controlli banali come nullpointer):

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

Esso potrebbe essere microslower di if/elseo switch, ma il codice è almeno molto meglio gestibile.

Dato che parli di applicazioni web, puoi utilizzarlo HttpServletRequest#getPathInfo()come tasto azione (eventualmente scrivere un po 'di codice in più per dividere l'ultima parte di pathinfo in un ciclo finché non viene trovata un'azione). Puoi trovare qui risposte simili:

Se sei preoccupato per le prestazioni delle applicazioni web Java EE in generale, potresti trovare utile anche questo articolo . Ci sono altre aree che offrono un guadagno di prestazioni molto maggiore rispetto alla sola (micro) ottimizzazione del codice Java grezzo.


1
o considera invece il polimorfismo
jk.

Questo è davvero più consigliato in caso di quantità "imprevedibile" di blocchi if / else.
BalusC

73
Non sono così veloce nel liquidare tutte le prime ottimizzazioni come "cattive". Essere troppo aggressivi è sciocco, ma di fronte a costrutti di leggibilità comparabile, sceglierne uno noto per funzionare meglio è una decisione appropriata.
Brian Knoblauch

8
La versione di ricerca di HashMap può essere facilmente 10 volte più lenta rispetto a un'istruzione tablewitsch. Non lo chiamerei "microslower"!
x4u

7
Sono interessato a conoscere effettivamente il funzionamento interno di Java nel caso generale con le istruzioni switch: non mi interessa sapere se qualcuno pensa o meno che ciò sia correlato alla priorità eccessiva dell'ottimizzazione iniziale. Detto questo, non ho assolutamente idea del motivo per cui questa risposta è votata così tanto e perché è la risposta accettata ... questo non fa nulla per rispondere alla domanda iniziale.
searchengine27

125

Sono totalmente d'accordo con l'opinione che l'ottimizzazione prematura sia qualcosa da evitare.

Ma è vero che la Java VM ha codici byte speciali che potrebbero essere usati per switch ().

Vedi specifiche WM ( lookupswitch e tableswitch )

Quindi potrebbero esserci alcuni miglioramenti delle prestazioni, se il codice fa parte del grafico delle prestazioni della CPU.


60
Mi chiedo perché questo commento non sia valutato più in alto: è il più informativo di tutti. Voglio dire: sappiamo già che l'ottimizzazione prematura è negativa e cose del genere, non c'è bisogno di spiegarlo per la millesima volta.
Folkert van Heusden

5
+1 A partire da stackoverflow.com/a/15621602/89818 sembra che i miglioramenti delle prestazioni siano davvero presenti e dovresti vedere un vantaggio se usi 18+ casi.
caw

52

È estremamente improbabile che un if / else o uno switch possa essere la fonte dei tuoi problemi di performance. Se hai problemi di prestazioni, dovresti prima eseguire un'analisi del profilo delle prestazioni per determinare dove si trovano i punti lenti. L'ottimizzazione prematura è la radice di tutti i mali!

Tuttavia, è possibile parlare delle prestazioni relative di switch vs. if / else con le ottimizzazioni del compilatore Java. Innanzitutto si noti che in Java, le istruzioni switch operano su un dominio molto limitato: gli interi. In generale, è possibile visualizzare un'istruzione switch come segue:

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

dove c_0,, c_1... e c_Nsono numeri interi che sono obiettivi dell'istruzione switch e <condition>devono risolversi in un'espressione intera.

  • Se questo insieme è "denso", cioè (max (c i ) + 1 - min (c i )) / n> α, dove 0 <k <α <1, dove kè maggiore di un valore empirico, a È possibile generare una tabella di salto, il che è altamente efficiente.

  • Se questo insieme non è molto denso, ma n> = β, un albero di ricerca binario può trovare l'obiettivo in O (2 * log (n)) che è ancora efficiente.

Per tutti gli altri casi, un'istruzione switch è esattamente efficiente quanto la serie equivalente di istruzioni if ​​/ else. I valori precisi di α e β dipendono da una serie di fattori e sono determinati dal modulo di ottimizzazione del codice del compilatore.

Infine, ovviamente, se il dominio di <condition>non è costituito dagli interi, un'istruzione switch è completamente inutile.


+1. Ci sono buone probabilità che il tempo trascorso sull'I / O di rete stia facilmente eclissando questo particolare problema.
Adam Paynter

3
Va notato che gli switch funzionano con più di un semplice int. Dalle esercitazioni Java: "Uno switch funziona con i tipi di dati primitivi byte, short, char e int. Funziona anche con i tipi enumerati (discussi in Tipi enumerativi), la classe String e alcune classi speciali che racchiudono determinati tipi primitivi : Character, Byte, Short e Integer (discussi in Numbers and Strings). " Il supporto per String è un'aggiunta più recente; aggiunto in Java 7. docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html
atraudes

1
@jhonFeminella Potresti confrontare gli effetti della nozione BIG O per Java7 String in Swtich rispetto a String in if / else if ..?
Kanagavelu Sugumar

Più precisamente, javac 8 dà un peso di 3 in tanto la complessità su complessità spaziale: stackoverflow.com/a/31032054/895245
Ciro Santilli郝海东冠状病六四事件法轮功

11

Usa l'interruttore!

Odio mantenere if-else-blocks! Fai un 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));
    }
}

Il mio codice standard C # per il benchmarking


Potresti per favore (qualche volta) approfondire un po 'come hai fatto il benchmark?
DerMike

Grazie mille per il tuo aggiornamento. Voglio dire, differiscono di un ordine di grandezza, il che è possibile ovviamente. Sei sicuro che il compilatore non abbia semplicemente ottimizzato gli switches?
DerMike

@DerMike Non ricordo come ho ottenuto i vecchi risultati. Oggi sono molto diverso. Ma provalo tu stesso e fammi sapere come va a finire.
Bitterblue

1
quando lo eseguo sul mio laptop; tempo di commutazione necessario: 3585, se / altro tempo necessario: 3458 quindi se / altro è migliore :) o non peggiore
halil

1
Il costo dominante nel test è la generazione di numeri casuali. Ho modificato il test per generare il numero casuale prima del ciclo e ho utilizzato il valore temp per restituire r. Lo switch è quindi quasi due volte più veloce della catena if-else.
boneill

8

Ricordo di aver letto che ci sono 2 tipi di istruzioni Switch nel bytecode Java. (Penso che fosse in 'Java Performance Tuning' One è un'implementazione molto veloce che utilizza i valori interi dell'istruzione switch per conoscere l'offset del codice da eseguire. Ciò richiederebbe che tutti i numeri interi siano consecutivi e in un intervallo ben definito Immagino che anche l'utilizzo di tutti i valori di un Enum rientrerebbe in quella categoria.

Sono d'accordo con molti altri poster però ... potrebbe essere prematuro preoccuparsi di questo, a meno che questo non sia un codice molto caldo.


4
+1 per il commento hot code. Se è nel tuo ciclo principale non è prematuro.
KingAndrew

Sì, javac implementa switchdiversi modi, alcuni più efficienti di altri. In generale, l'efficienza non sarà peggiore di una semplice " ifscala", ma ci sono abbastanza variazioni (specialmente con il JITC) che è difficile essere molto più precisi di così.
Hot Licks

8

Secondo Cliff Click nel suo discorso di Java One del 2009 A Crash Course in Modern Hardware :

Oggi, le prestazioni sono dominate dai modelli di accesso alla memoria. Dominano gli errori di cache: la memoria è il nuovo disco. [Diapositiva 65]

Puoi avere le sue diapositive complete qui .

Cliff fa un esempio (terminando sulla diapositiva 30) che mostra che anche con la CPU che esegue la ridenominazione dei registri, la previsione dei rami e l'esecuzione speculativa, è in grado di avviare solo 7 operazioni in 4 cicli di clock prima di dover bloccare a causa di due errori di cache che richiedono 300 cicli di clock per tornare.

Quindi dice che per velocizzare il tuo programma non dovresti considerare questo tipo di problema minore, ma su quelli più grandi come se stai facendo conversioni di formato dati non necessarie, come la conversione "SOAP → XML → DOM → SQL → ... "che" passa tutti i dati attraverso la cache ".


4

Nel mio test le prestazioni migliori sono ENUM> MAP> SWITCH> IF / ELSE IF in Windows7.

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

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 Non sono sicuro di come questo codice funzioni su ambienti diversi, ma hai menzionato se / elseif è migliore di Switch e Map, che non sono in grado di convincere poiché se / elseif deve eseguire più volte il confronto.
Kanagavelu Sugumar

2

Per la maggior parte switche la maggior parte dei if-then-elseblocchi, non riesco a immaginare che ci siano preoccupazioni apprezzabili o significative relative alle prestazioni.

Ma ecco il punto: se stai usando un switchblocco, il suo uso suggerisce che stai attivando un valore preso da un insieme di costanti conosciute in fase di compilazione. In questo caso, non dovresti davvero usare le switchistruzioni se puoi usare un enumcon metodi specifici per le costanti.

Rispetto a switchun'istruzione, un'enumerazione fornisce una migliore sicurezza dei tipi e un codice più facile da mantenere. Le enumerazioni possono essere progettate in modo che se una costante viene aggiunta all'insieme di costanti, il codice non verrà compilato senza fornire un metodo specifico della costante per il nuovo valore. D'altra parte, dimenticare di aggiungere un nuovo blocco casea un switchblocco a volte può essere rilevato solo in fase di esecuzione se sei abbastanza fortunato da aver impostato il blocco per lanciare un'eccezione.

Le prestazioni tra switche un enummetodo specifico della costante non dovrebbero essere significativamente differenti, ma quest'ultimo è più leggibile, più sicuro e più facile da mantenere.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.