Preocupado por el rendimiento de mi aplicación web, me pregunto cuál de las declaraciones "si / si no" o cambiar es mejor en cuanto al rendimiento.
if
etc.
Preocupado por el rendimiento de mi aplicación web, me pregunto cuál de las declaraciones "si / si no" o cambiar es mejor en cuanto al rendimiento.
if
etc.
Respuestas:
Eso es micro optimización y optimización prematura, que son malas. Preocúpese más bien por la legibilidad y el mantenimiento del código en cuestión. Si hay más de dos if/else
bloques pegados entre sí o su tamaño es impredecible, entonces puede considerar una switch
declaración.
Alternativamente, también puede tomar Polimorfismo . Primero crea una interfaz:
public interface Action {
void execute(String input);
}
Y hazte con todas las implementaciones en algunas Map
. Puede hacer esto de forma estática o dinámica:
Map<String, Action> actions = new HashMap<String, Action>();
Finalmente reemplace el if/else
o switch
por algo como esto (dejando a un lado los controles triviales como los punteros nulos):
actions.get(name).execute(input);
Se podría ser microslower de if/else
o switch
, pero el código es al menos mucho mejor mantener.
Mientras habla de aplicaciones web, puede utilizarla HttpServletRequest#getPathInfo()
como tecla de acción (eventualmente, escriba 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:
Si le preocupa el rendimiento de la aplicación web Java EE en general, este artículo también puede resultarle útil. Hay otras áreas que brindan una ganancia de rendimiento mucho mayor que solo (micro) optimizar el código Java sin procesar.
Estoy totalmente de acuerdo con la opinión de que la optimización prematura es algo que se debe evitar.
Pero es cierto que Java VM tiene códigos de bytes especiales que podrían usarse para switch ().
Ver especificaciones de WM ( conmutador de búsqueda y conmutador de tabla )
Por lo tanto, podría haber algunas mejoras en el rendimiento, si el código es parte del gráfico de rendimiento de la CPU.
Es muy poco probable que un if / else o un switch 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 frente a if / else con las optimizaciones del compilador de Java. En primer lugar, tenga en cuenta que en Java, las sentencias switch operan en un dominio muy limitado: enteros. En general, puede ver una declaración de cambio de la siguiente manera:
switch (<condition>) {
case c_0: ...
case c_1: ...
...
case c_n: ...
default: ...
}
donde c_0
,, c_1
..., y c_N
son números <condition>
enteros que son destinos de la instrucción de cambio y deben resolverse en una expresión entera.
Si este conjunto es "denso", es decir, (max (c i ) + 1 - min (c i )) / n> α, donde 0 <k <α <1, donde k
es mayor que algún valor empírico, a Se puede generar una tabla de salto, que es muy eficiente.
Si este conjunto no es muy denso, pero n> = β, un árbol de búsqueda binario puede encontrar el objetivo 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 declaraciones if / else. Los valores precisos de α y β dependen de varios factores y están determinados por el módulo de optimización de código del compilador.
Finalmente, por supuesto, si el dominio de <condition>
no son los números enteros, una declaración de cambio es completamente inútil.
¡Usa el interruptor!
¡Odio mantener bloqueos if-else! Hágase 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));
}
}
switch
es?
Recuerdo haber leído que hay 2 tipos de declaraciones Switch en el código de bytes de Java. (Creo que fue en 'Java Performance Tuning' One es una implementación muy rápida que usa los valores enteros de la declaración de cambio 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 el uso de todos los valores de un Enum también entraría en esa categoría.
Sin embargo, estoy de acuerdo con muchos otros carteles ... puede ser prematuro preocuparse por esto, a menos que este sea un código muy, muy caliente.
switch
varias formas diferentes, algunas más eficientes que otras. En general, la eficiencia no será peor que una " if
escalera" sencilla , pero hay suficientes variaciones (especialmente con el JITC) que es difícil ser mucho más preciso que eso.
Según Cliff Click en su charla Java One de 2009 Un curso intensivo en hardware moderno :
Hoy en día, el rendimiento está dominado por patrones de acceso a la memoria. Los errores de caché dominan: la memoria es el nuevo disco. [Diapositiva 65]
Puede obtener sus diapositivas completas aquí .
Cliff da un ejemplo (terminando en la diapositiva 30) que muestra que incluso con la CPU haciendo cambio de nombre de registro, predicción de rama 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 volver.
Entonces, él dice que para acelerar su programa, no debería estar mirando este tipo de problemas menores, sino más grandes, como si está haciendo conversiones de formato de datos innecesarias, como convertir "SOAP → XML → DOM → SQL → ... "que" pasa todos los datos a través de la 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<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
Para la mayoría switch
y la mayoría de los if-then-else
bloques, no puedo imaginar que haya preocupaciones importantes o apreciables relacionadas con el rendimiento.
Pero aquí está la cuestión: si está utilizando un switch
bloque, su uso sugiere que está activando un valor tomado de un conjunto de constantes conocidas en tiempo de compilación. En este caso, realmente no debería usar switch
declaraciones si puede usar unenum
con métodos específicos de constante.
En comparación con una switch
declaración, una enumeración proporciona una mejor seguridad de tipos 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 compilará sin proporcionar un método específico de constante para el nuevo valor. Por otro lado, olvidar agregar un nuevo case
a un switch
bloque a veces solo se puede detectar en el tiempo de ejecución si tiene la suerte de haber configurado su bloque para lanzar una excepción.
Rendimiento entre switch
un enum
método específico de constante y no debería ser significativamente diferente, pero este último es más legible, más seguro y más fácil de mantener.