Preoccupato per le prestazioni della mia applicazione web, mi chiedo quale tra "if / else" o switch statement sia migliore per quanto riguarda le prestazioni?
if
ecc.
Preoccupato per le prestazioni della mia applicazione web, mi chiedo quale tra "if / else" o switch statement sia migliore per quanto riguarda le prestazioni?
if
ecc.
Risposte:
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/else
blocchi incollati insieme o la sua dimensione è imprevedibile, allora potresti prendere in considerazione switch
un'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/else
o switch
con qualcosa di simile (lasciando da parte controlli banali come nullpointer):
actions.get(name).execute(input);
Esso potrebbe essere microslower di if/else
o 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.
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.
È 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_N
sono 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.
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));
}
}
switch
es?
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.
switch
diversi modi, alcuni più efficienti di altri. In generale, l'efficienza non sarà peggiore di una semplice " if
scala", ma ci sono abbastanza variazioni (specialmente con il JITC) che è difficile essere molto più precisi di così.
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 ".
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
Per la maggior parte switch
e la maggior parte dei if-then-else
blocchi, non riesco a immaginare che ci siano preoccupazioni apprezzabili o significative relative alle prestazioni.
Ma ecco il punto: se stai usando un switch
blocco, 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 switch
istruzioni se puoi usare un enum
con metodi specifici per le costanti.
Rispetto a switch
un'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 case
a un switch
blocco 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 switch
e un enum
metodo specifico della costante non dovrebbero essere significativamente differenti, ma quest'ultimo è più leggibile, più sicuro e più facile da mantenere.