¿Cuál es la diferencia relativa de rendimiento de if / else versus la instrucción switch en Java?

Preocupado por el rendimiento de mi aplicación web, me pregunto cuál de las afirmaciones “if / else” o switch es mejor con respecto al rendimiento.

Eso es micro optimización y optimización prematura, que son malvadas. Preocúpese más por la capacidad de lectura y la facilidad de mantenimiento del código en cuestión. Si hay más de dos bloques if/else pegados entre sí o su tamaño es impredecible, entonces puede considerar una statement de switch .

Alternativamente, también puedes agarrar el polymorphism . Primero crea alguna interfaz:

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

Y obtenga todas las implementaciones en algún Map . Puede hacer esto de forma estática o dinámica:

 Map actions = new HashMap(); 

Finalmente, reemplace if/else o switch por algo como esto (dejando a un lado las comprobaciones triviales como nullpointers):

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

Puede ser microslower que if/else o switch , pero el código es mucho mejor mantenible.

Mientras habla de aplicaciones de aplicaciones, puede utilizar HttpServletRequest#getPathInfo() como clave de acción (eventualmente escribir más código para dividir la última parte de pathinfo en un bucle hasta que se encuentre una acción). Aquí puede encontrar respuestas similares:

  • El uso de un marco personalizado orientado a servlets, demasiados servlets, es un problema
  • Controlador frontal Java

Si está preocupado por el rendimiento de la aplicación de Java EE en general, entonces puede encontrar este artículo útil también. Hay otras áreas que ofrecen una ganancia de rendimiento mucho mayor que solo (micro) la optimización del código Java sin procesar.

Estoy totalmente de acuerdo con la opinión de que la optimización prematura es algo que hay que evitar.

Pero es cierto que Java VM tiene códigos de bytes especiales que podrían usarse para los conmutadores ().

Ver WM Spec ( switch de búsqueda y cambio de tabla )

Por lo tanto, podría haber algunas mejoras de rendimiento, si el código es parte del gráfico de CPU de rendimiento.

Es extremadamente improbable que un if / else o un interruptor sea la fuente de sus problemas de rendimiento. Si tiene problemas de rendimiento, primero debe realizar un análisis de perfil de rendimiento para determinar dónde están los puntos lentos. ¡La optimización temprana es la raíz de todo mal!

Sin embargo, es posible hablar sobre el rendimiento relativo de switch vs. if / else con las optimizaciones del comstackdor Java. Primero tenga en cuenta que en Java, las instrucciones switch operan en un dominio muy limitado: enteros. En general, puede ver una instrucción switch de la siguiente manera:

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

donde c_0 , c_1 , … y c_N son números c_N que son objectives de la instrucción switch, y debe resolverse en una expresión entera.

  • Si este conjunto es “denso”, es decir, (max (c i ) + 1 – min (c i )) / n> α, donde 0 k es mayor que algún valor empírico, se puede generar una tabla de salto, que es altamente eficiente.

  • Si este conjunto no es muy denso, pero n> = β, un árbol de búsqueda binaria puede encontrar el objective en O (2 * log (n)) que también es eficiente.

Para todos los demás casos, una instrucción switch es exactamente tan eficiente como la serie equivalente de sentencias if / else. Los valores precisos de α y β dependen de una serie de factores y están determinados por el módulo de optimización de código del comstackdor.

Finalmente, por supuesto, si el dominio de no son los enteros, una instrucción de conmutación es completamente inútil.

Use el interruptor!

Odio mantener if-else-blocks! Tener una prueba:

 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)); } } 

Mi código estándar de C # para la evaluación comparativa

Recuerdo haber leído que hay dos tipos de declaraciones Switch en bytecode de Java. (Creo que fue en ‘Java Performance Tuning’). Es una implementación muy rápida que utiliza los valores enteros de la instrucción switch para conocer el desplazamiento del código que se ejecutará. Esto requeriría que todos los enteros sean consecutivos y en un rango bien definido. . Supongo que usar todos los valores de un Enum caería también en esa categoría.

Aunque estoy de acuerdo con muchos otros carteles … puede ser prematuro preocuparse por esto, a menos que este sea un código muy candente.

De acuerdo con Cliff Click en su 2009 Java One talk Un curso acelerado en hardware moderno :

Hoy, el rendimiento está dominado por patrones de acceso a la memoria. Las omisiones de caché dominan: la memoria es el nuevo disco. [Diapositiva 65]

Puedes obtener sus diapositivas completas aquí .

Cliff da un ejemplo (terminando en la Diapositiva 30) que muestra que incluso con la CPU haciendo registro-cambio de nombre, predicción de bifurcación y ejecución especulativa, solo puede iniciar 7 operaciones en 4 ciclos de reloj antes de tener que bloquear debido a dos fallas de caché que toman 300 ciclos de reloj para regresar.

Entonces, dice que para acelerar tu progtwig no deberías mirar este tipo de problema menor, sino en los más grandes, como si estás haciendo conversiones de formato de datos innecesarias, como convertir “SOAP → XML → DOM → SQL → … “que” pasa todos los datos a través del caché “.

En mi prueba, el mejor rendimiento es ENUM> MAP> SWITCH> IF / ELSE IF en 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 */ 

Para la mayoría de los switch y la mayoría if-then-else bloques if-then-else , no puedo imaginar que haya preocupaciones apreciables o significativas relacionadas con el rendimiento.

Pero esta es la cuestión: si está usando un bloque de switch , su uso mismo sugiere que está activando un valor tomado de un conjunto de constantes conocidas en tiempo de comstackción. En este caso, realmente no debería usar declaraciones de switch si puede usar una enum con métodos específicos de constante.

Comparado con una instrucción switch , una enumeración proporciona un mejor tipo de seguridad y un código que es más fácil de mantener. Las enumeraciones se pueden diseñar de modo que si se agrega una constante al conjunto de constantes, su código no se comstackrá sin proporcionar un método de constante constante para el nuevo valor. Por otro lado, olvidarse de agregar una nueva case a un bloque de switch veces solo se puede capturar en el tiempo de ejecución si tienes la suerte de haber configurado tu bloque para lanzar una excepción.

El rendimiento entre el switch y un método de constante específica enum no debe ser significativamente diferente, pero este último es más legible, más seguro y más fácil de mantener.