Qual è la differenza di prestazioni relativa dell’istruzione if / else versus switch in Java?

Preoccupandomi per le prestazioni della mia applicazione web, mi chiedo quale frase “if / else” o switch sia migliore per quanto riguarda le prestazioni?

    Questa è micro ottimizzazione e ottimizzazione prematura, che sono malvagie. Piuttosto preoccuparsi della leggibilità e della manutenibilità del codice in questione. Se ci sono più di due blocchi if/else incollati insieme o la sua dimensione è imprevedibile, allora si può considerare altamente un’istruzione switch .

    In alternativa, puoi anche prendere il polimorfismo . Per prima cosa crea un’interfaccia:

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

    E ottenere tutte le implementazioni in alcune Map . Puoi farlo staticamente o dynamicmente:

     Map actions = new HashMap(); 

    Infine sostituisci if/else o switch con qualcosa di simile (lasciando da parte banali controlli come nullpointers):

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

    Potrebbe essere microslower di if/else o switch , ma il codice è almeno di gran lunga meglio gestibile.

    Mentre parli di applicazioni web, puoi utilizzare HttpServletRequest#getPathInfo() come tasto azione (eventualmente scrivere un altro codice per dividere l’ultima parte di pathinfo in un ciclo finché non viene trovata un’azione). Puoi trovare qui le risposte simili:

    • Utilizzando un framework orientato su Servlet personalizzato, troppi servlet, questo è un problema
    • Java Front Controller

    Se ti preoccupi delle prestazioni dell’applicazione Web Java EE in generale, potresti trovare utile questo articolo . Ci sono altre aree che offrono un guadagno di prestazioni molto maggiore rispetto al solo (micro) ottimizzazione del codice Java grezzo.

    Sono assolutamente d’accordo con l’opinione che l’ottimizzazione prematura è qualcosa da evitare.

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

    Vedi WM Spec ( lookupswitch e tableswitch )

    Quindi potrebbero esserci dei guadagni in termini di prestazioni, se il codice fa parte del grafico della CPU delle prestazioni.

    È estremamente improbabile che un se / else o un interruttore sia la fonte dei tuoi problemi di prestazioni. Se si verificano problemi di prestazioni, è necessario eseguire prima un’analisi del profilo delle prestazioni per determinare dove si trovano gli angoli lenti. L’ottimizzazione prematura è la radice di tutto il male!

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

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

    dove c_0 , c_1 , … e c_N sono numeri interi che sono target c_N switch e deve risolversi in un’espressione intera.

    • Se questo insieme è “denso” – cioè, (max (c i ) + 1 – min (c i )) / n> α, dove 0 k è maggiore di qualche valore empirico, una tabella di salto può essere generata, che è altamente efficiente.

    • Se questo insieme non è molto denso, ma n> = β, un albero di ricerca binario può trovare il bersaglio in O (2 * log (n)) che è comunque 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 un numero di fattori e sono determinati dal modulo di ottimizzazione del codice del compilatore.

    Infine, naturalmente, se il dominio di non è l’interno, un’istruzione switch è completamente inutile.

    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 C # standard per il benchmarking

    Ricordo di aver letto che ci sono 2 tipi di istruzioni Switch in bytecode Java. (Penso che fosse in ‘Java Performance Tuning’ Uno è un’implementazione molto veloce che utilizza i valori interi dell’istruzione switch per conoscere l’offset del codice da eseguire. Ciò richiederebbe che tutti gli interi fossero consecutivi e in un intervallo ben definito Immagino che usare tutti i valori di un Enum possa cadere anche 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 molto caldo.

    Secondo Cliff Click nel suo Java One 2009 discorsi Un corso Crash in Hardware moderno :

    Oggi, le prestazioni sono dominate da schemi di accesso alla memoria. La mancanza di cache domina: la memoria è il nuovo disco. [Diapositiva 65]

    Puoi avere le sue diapositive complete qui .

    Cliff dà un esempio (finendo sulla diapositiva 30) mostrando che anche con la CPU che fa rinominare i registri, la previsione dei rami e l’esecuzione speculativa, è solo in grado di avviare 7 operazioni in 4 cicli di clock prima di dover bloccare a causa di due errori di cache che prendono 300 cicli di clock da restituire.

    Quindi, per accelerare il tuo programma, non dovresti considerare questo tipo di problema minore, ma su quelli più grandi, ad esempio se stai facendo conversioni non necessarie nel formato dei dati, come convertire “SOAP → XML → DOM → SQL → … “che” passa tutti i dati attraverso la cache “.

    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 map = new HashMap(); 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 */ 

    Per la maggior parte degli switch e dei blocchi if-then-else , non riesco a immaginare che ci siano preoccupazioni relative alla performance significative o significative.

    Ma ecco il punto: se stai usando un blocco switch , il suo uso suggerisce che stai triggersndo un valore preso da un insieme di costanti conosciute al momento della compilazione. In questo caso, non dovresti assolutamente utilizzare le istruzioni switch se è ansible utilizzare un enum con metodi costanti specifici.

    Rispetto a un’istruzione switch , un enum fornisce una sicurezza e un codice di tipo migliore che è 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 costante per il nuovo valore. D’altra parte, dimenticando di aggiungere un nuovo case a un blocco di switch volte può essere catturato solo in fase di esecuzione se sei abbastanza fortunato da aver impostato il blocco fino a generare un’eccezione.

    Le prestazioni tra switch e un metodo specifico costante enum non dovrebbero essere significativamente differenti, ma quest’ultimo è più leggibile, più sicuro e più facile da mantenere.