En me souciant des performances de mon application Web, je me demande laquelle des instructions «if / else» ou switch est la meilleure en termes de performances?
if
etc.
En me souciant des performances de mon application Web, je me demande laquelle des instructions «if / else» ou switch est la meilleure en termes de performances?
if
etc.
Réponses:
C'est la micro-optimisation et l'optimisation prématurée, qui sont mauvaises. Se soucier plutôt de la lisibilité et de la maintenabilité du code en question. S'il y a plus de deux if/else
blocs collés ensemble ou si sa taille est imprévisible, alors vous pouvez fortement envisager une switch
déclaration.
Alternativement, vous pouvez également récupérer Polymorphism . Commencez par créer une interface:
public interface Action {
void execute(String input);
}
Et obtenez toutes les implémentations dans certains Map
. Vous pouvez le faire de manière statique ou dynamique:
Map<String, Action> actions = new HashMap<String, Action>();
Enfin, remplacez le if/else
ou switch
par quelque chose comme ceci (en laissant de côté les vérifications triviales comme les pointeurs nuls):
actions.get(name).execute(input);
Il est peut- être plus lent que if/else
ou switch
, mais le code est au moins beaucoup mieux maintenable.
Lorsque vous parlez d'applications Web, vous pouvez utiliser HttpServletRequest#getPathInfo()
comme clé d'action (éventuellement écrire un peu plus de code pour séparer la dernière partie de pathinfo en une boucle jusqu'à ce qu'une action soit trouvée). Vous pouvez trouver ici des réponses similaires:
Si vous vous inquiétez des performances de l'application Web Java EE en général, cet article peut également vous être utile. Il existe d'autres domaines qui donnent un gain de performances beaucoup plus important que la seule (micro) optimisation du code Java brut.
Je suis totalement d'accord avec l'opinion selon laquelle l'optimisation prématurée est quelque chose à éviter.
Mais il est vrai que la machine virtuelle Java a des bytecodes spéciaux qui pourraient être utilisés pour les switch ().
Voir WM Spec ( lookupswitch et tableswitch )
Il pourrait donc y avoir des gains de performances, si le code fait partie du graphique des performances du processeur.
Il est extrêmement improbable qu'un if / else ou un commutateur soit la source de vos problèmes de performances. Si vous rencontrez des problèmes de performances, vous devez d'abord effectuer une analyse de profilage des performances pour déterminer où se trouvent les points lents. L'optimisation prématurée est la racine de tout Mal!
Néanmoins, il est possible de parler des performances relatives du commutateur par rapport à if / else avec les optimisations du compilateur Java. Notez d'abord qu'en Java, les instructions switch fonctionnent sur un domaine très limité - les entiers. En général, vous pouvez afficher une instruction switch comme suit:
switch (<condition>) {
case c_0: ...
case c_1: ...
...
case c_n: ...
default: ...
}
où c_0
,, c_1
... et c_N
sont des nombres entiers qui sont les cibles de l'instruction switch et <condition>
doivent se résoudre en une expression entière.
Si cet ensemble est "dense" - c'est-à-dire (max (c i ) + 1 - min (c i )) / n> α, où 0 <k <α <1, où k
est plus grand qu'une certaine valeur empirique, a une table de saut peut être générée, ce qui est très efficace.
Si cet ensemble n'est pas très dense, mais n> = β, un arbre de recherche binaire peut trouver la cible dans O (2 * log (n)) qui est toujours aussi efficace.
Dans tous les autres cas, une instruction switch est exactement aussi efficace que la série équivalente d'instructions if / else. Les valeurs précises de α et β dépendent d'un certain nombre de facteurs et sont déterminées par le module d'optimisation de code du compilateur.
Enfin, bien sûr, si le domaine de <condition>
n'est pas les nombres entiers, une instruction switch est complètement inutile.
Utilisez le commutateur!
Je déteste maintenir des blocs if-else! Faites 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 loin?
Je me souviens avoir lu qu'il existe 2 types d'instructions Switch dans le bytecode Java. (Je pense que c'était dans 'Java Performance Tuning' One est une implémentation très rapide qui utilise les valeurs entières de l'instruction switch pour connaître le décalage du code à exécuter. Cela nécessiterait que tous les entiers soient consécutifs et dans une plage bien définie Je suppose que l'utilisation de toutes les valeurs d'un Enum tomberait également dans cette catégorie.
Je suis cependant d'accord avec beaucoup d'autres affiches ... il est peut-être prématuré de s'inquiéter à ce sujet, à moins que ce ne soit du code très très chaud.
switch
plusieurs manières différentes, certaines plus efficaces que d'autres. En général, l'efficacité ne sera pas pire qu'une simple « if
échelle», mais il y a suffisamment de variations (surtout avec le JITC) qu'il est difficile d'être beaucoup plus précis que cela.
Selon Cliff Click dans son exposé sur Java One en 2009, un cours intensif sur le matériel moderne :
Aujourd'hui, les performances sont dominées par les modèles d'accès à la mémoire. Les erreurs de cache dominent - la mémoire est le nouveau disque. [Diapositive 65]
Vous pouvez obtenir ses diapositives complètes ici .
Cliff donne un exemple (finissant sur la diapositive 30) montrant que même avec le processeur effectuant un renommage de registre, une prédiction de branche et une exécution spéculative, il ne peut démarrer que 7 opérations en 4 cycles d'horloge avant d'avoir à bloquer en raison de deux échecs de cache qui prennent 300 cycles d'horloge pour revenir.
Donc, il dit que pour accélérer votre programme, vous ne devriez pas vous pencher sur ce genre de problème mineur, mais sur des problèmes plus importants tels que si vous effectuez des conversions de format de données inutiles, telles que la conversion de "SOAP → XML → DOM → SQL → ... "qui" passe toutes les données à travers le cache ".
Dans mon test, la meilleure performance est ENUM> MAP> SWITCH> IF / ELSE IF dans 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
Pour la plupart switch
et la plupart des if-then-else
blocs, je ne peux pas imaginer qu'il existe des problèmes de performances appréciables ou significatifs.
Mais voici le problème: si vous utilisez un switch
bloc, son utilisation même suggère que vous basculez sur une valeur prise à partir d'un ensemble de constantes connues au moment de la compilation. Dans ce cas, vous ne devriez vraiment pas utiliser d' switch
instructions du tout si vous pouvez utiliser unenum
des méthodes spécifiques aux constantes.
Par rapport à une switch
instruction, une énumération fournit une meilleure sécurité de type et un code plus facile à gérer. Les énumérations peuvent être conçues de sorte que si une constante est ajoutée à l'ensemble de constantes, votre code ne se compilera pas sans fournir une méthode spécifique à la constante pour la nouvelle valeur. D'un autre côté, oublier d'ajouter un nouveau case
à un switch
bloc ne peut parfois être intercepté au moment de l'exécution que si vous avez la chance d'avoir configuré votre bloc pour lever une exception.
Les performances entre switch
et une enum
méthode spécifique à une constante ne doivent pas être significativement différentes, mais cette dernière est plus lisible, plus sûre et plus facile à entretenir.