Preocupado com o desempenho do meu aplicativo da web, pergunto-me qual das declarações "if / else" ou switch é melhor em relação ao desempenho?
if
etc.
Preocupado com o desempenho do meu aplicativo da web, pergunto-me qual das declarações "if / else" ou switch é melhor em relação ao desempenho?
if
etc.
Respostas:
Isso é micro otimização e otimização prematura, que são más. Preocupe-se com a legibilidade e a manutenção do código em questão. Se houver mais de dois if/else
blocos colados ou seu tamanho for imprevisível, você poderá considerar uma switch
declaração.
Como alternativa, você também pode pegar o polimorfismo . Primeiro, crie alguma interface:
public interface Action {
void execute(String input);
}
E conheça todas as implementações em algumas Map
. Você pode fazer isso estaticamente ou dinamicamente:
Map<String, Action> actions = new HashMap<String, Action>();
Finalmente substitua o if/else
ou switch
por algo assim (deixando verificações triviais como nullpointers de lado):
actions.get(name).execute(input);
Ele pode ser microslower do que if/else
ou switch
, mas o código é pelo menos muito melhor manutenção.
Enquanto você fala sobre aplicativos da web, pode usar HttpServletRequest#getPathInfo()
como chave de ação (eventualmente escreva mais um código para dividir a última parte do pathinfo em um loop até que uma ação seja encontrada). Você pode encontrar aqui respostas semelhantes:
Se você está preocupado com o desempenho de aplicativos da Web Java EE em geral, também poderá achar este artigo útil. Existem outras áreas que oferecem um ganho de desempenho muito maior do que apenas a otimização (micro) do código Java bruto.
Concordo totalmente com a opinião de que a otimização prematura é algo a ser evitado.
Mas é verdade que a Java VM possui bytecodes especiais que podem ser usados para os switches ().
Consulte WM Spec ( lookupswitch e tableswitch )
Portanto, pode haver alguns ganhos de desempenho, se o código fizer parte do gráfico da CPU de desempenho.
É extremamente improvável que um if / else ou um switch sejam a fonte dos seus problemas de desempenho. Se você estiver tendo problemas de desempenho, faça uma análise de perfil de desempenho primeiro para determinar onde estão os pontos lentos. Otimização prematura é a raiz de todo o mal!
No entanto, é possível falar sobre o desempenho relativo do switch vs. if / else com as otimizações do compilador Java. Primeiro, observe que, em Java, as instruções switch operam em um domínio - número inteiro muito limitado. Em geral, você pode visualizar uma instrução switch da seguinte maneira:
switch (<condition>) {
case c_0: ...
case c_1: ...
...
case c_n: ...
default: ...
}
onde c_0
,, c_1
... e c_N
são números inteiros que são alvos da instrução switch e <condition>
devem ser resolvidos para uma expressão inteira.
Se esse conjunto for "denso" - ou seja, (max (c i ) + 1 - min (c i )) / n> α, onde 0 <k <α <1, onde k
é maior que algum valor empírico, a A tabela de salto pode ser gerada, o que é altamente eficiente.
Se esse conjunto não for muito denso, mas n> = β, uma árvore de pesquisa binária poderá encontrar o destino em O (2 * log (n)), que também é eficiente também.
Para todos os outros casos, uma instrução switch é exatamente tão eficiente quanto a série equivalente de instruções if / else. Os valores precisos de α e β dependem de vários fatores e são determinados pelo módulo de otimização de código do compilador.
Finalmente, é claro, se o domínio de <condition>
não for o número inteiro, uma instrução switch será completamente inútil.
Use o interruptor!
Eu odeio manter blocos if-else! Faça um teste:
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?
Lembro-me de ler que existem 2 tipos de instruções Switch no bytecode Java. (Eu acho que estava no 'Java Performance Tuning'. One é uma implementação muito rápida que usa os valores inteiros da instrução switch para saber o deslocamento do código a ser executado. Isso exigiria que todos os números inteiros fossem consecutivos e em um intervalo bem definido Suponho que o uso de todos os valores de um Enum também se enquadre nessa categoria.
Mas eu concordo com muitos outros pôsteres ... pode ser prematuro se preocupar com isso, a menos que seja um código muito, muito quente.
switch
várias maneiras diferentes, algumas mais eficientes que outras. Em geral, a eficiência não será pior do que uma " if
escada " direta , mas há variações suficientes (especialmente com o JITC) que dificilmente será mais preciso do que isso.
De acordo com Cliff Click em sua palestra em Java One A 2009 A Crash Course in Modern Hardware :
Hoje, o desempenho é dominado por padrões de acesso à memória. As falhas de cache dominam - a memória é o novo disco. [Slide 65]
Você pode obter os slides completos aqui .
Cliff dá um exemplo (terminando no Slide 30) mostrando que, mesmo com a CPU fazendo renomeação de registro, previsão de ramificação e execução especulativa, só é possível iniciar 7 operações em 4 ciclos de clock antes de ter que bloquear devido a duas falhas de cache que são necessárias 300 ciclos de relógio para retornar.
Então, ele diz que para acelerar seu programa, você não deve considerar esse tipo de problema menor, mas em problemas maiores, como se você está fazendo conversões desnecessárias no formato de dados, como a conversão de "SOAP → XML → DOM → SQL →… "what" passa todos os dados pelo cache ".
No meu teste, o melhor desempenho é ENUM> MAP> SWITCH> IF / ELSE IF no 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 a maioria switch
e a maioria dos if-then-else
blocos, não consigo imaginar que haja alguma preocupação significativa ou significativa relacionada ao desempenho.
Mas eis o seguinte: se você estiver usando um switch
bloco, seu próprio uso sugere que você esteja ativando um valor obtido de um conjunto de constantes conhecidas em tempo de compilação. Nesse caso, você realmente não deveria usar switch
instruções, se puder usar enum
métodos com constantes específicas.
Comparado a uma switch
declaração, um enum fornece melhor segurança e código de tipo, mais fáceis de manter. As enums podem ser projetadas para que, se uma constante for adicionada ao conjunto de constantes, seu código não seja compilado sem fornecer um método específico para a constante para o novo valor. Por outro lado, esquecer de adicionar um novo case
a um switch
bloco às vezes só pode ser detectado em tempo de execução se você tiver a sorte de configurar seu bloco para lançar uma exceção.
O desempenho entre switch
e um enum
método específico de constante não deve ser significativamente diferente, mas o último é mais legível, mais seguro e mais fácil de manter.