Préoccupé par les performances de mon application Web, je me demande laquelle des déclarations "if/else" ou switch est la meilleure en ce qui concerne les performances?
C'est l'optimisation micro et l'optimisation prématurée, qui sont diaboliques. Préoccupez plutôt la lisibilité et la maintenabilité du code en question. S'il y a plus de deux blocs if/else
Collés ensemble ou si sa taille est imprévisible, vous pouvez fortement envisager une instruction switch
.
Alternativement, vous pouvez aussi saisir Polymorphism. Commencez par créer une interface:
public interface Action {
void execute(String input);
}
Et récupérez 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 telles que nullpointers):
actions.get(name).execute(input);
Cela pourrait être plus petit 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 (écrivez un peu plus de code pour scinder la dernière partie de pathinfo en boucle jusqu'à ce qu'une action est trouvé). Vous pouvez trouver ici des réponses similaires:
Si vous vous inquiétez à propos de Java Les performances de l'application Web EE en général, alors vous pouvez trouver cet article utile également. Il existe d'autres zones donnant un beaucoup plus gain de performance que seulement (micro) optimiser le code brut Java).
Je suis totalement d'accord avec l'opinion selon laquelle une optimisation prématurée est quelque chose à éviter.
Mais il est vrai que le Java VM possède des bytecodes spéciaux qui pourraient être utilisés pour les commutateurs ()).
Voir WM Spec ( lookupswitch et tableswitch )
Il pourrait donc y avoir des gains de performances, si le code fait partie du graphique CPU de performance.
Il est extrêmement peu probable qu'un commutateur if/else ou un commutateur soit la source de vos problèmes de performances. Si vous rencontrez des problèmes de performances, vous devez commencer par effectuer une analyse de profilage des performances afin de déterminer les points lents. L'optimisation prématurée est la racine de tout Mal!
Néanmoins, il est possible de parler de la performance relative de switch par rapport à if/else avec les optimisations du compilateur Java). Notons tout d'abord qu'en Java, les instructions de commutateur 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>
doit être résolu en une expression entière.
Si cet ensemble est "dense" - c’est-à-dire (max (cje) + 1 - min (cje))/n> α, où 0 <k <α <1, où k
est supérieur à une valeur empirique, il est possible de générer une table de saut extrêmement efficace.
Si cet ensemble n'est pas très dense, mais que n> = β, un arbre de recherche binaire peut trouver la cible dans O (2 * log (n)), ce qui est également 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 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 un entier, une instruction switch est totalement inutile.
Utilisez le commutateur!
Je déteste maintenir si-sinon-blocs! Avoir 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));
}
}
Selon Cliff Click dans son 2009 Java Une conversation n cours intensif sur le matériel moderne :
Aujourd'hui, les performances sont dominées par les modèles d'accès mémoire. Les défauts de cache dominent - la mémoire est le nouveau disque. [Diapositive 65]
Vous pouvez obtenir ses diapositives complètes ici .
Cliff donne un exemple (se terminant sur la diapositive 30) montrant que même avec le processeur qui renomme des registres, prévoit des branches et exécute de manière spéculative, il ne peut démarrer que 7 opérations sur 4 cycles d'horloge avant de devoir être bloqué en raison de deux erreurs 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 type de problème mineur, mais sur des problèmes plus importants, tels que les conversions inutiles de formats de données, telles que la conversion de "SOAP → XML → DOM → SQL → ... "qui" passe toutes les données à travers le cache ".
Je me souviens avoir lu qu'il y avait 2 types d'instructions Switch dans le bytecode Java. (Je pense que c'était dans 'Java Performance Tuning'). Il s'agit d'une implémentation très rapide qui utilise les valeurs entières de l'instruction switch pour savoir le décalage du code à exécuter, ce qui 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 d'accord avec beaucoup d'autres affiches cependant ... il est peut-être trop tôt pour s'en inquiéter, à moins qu'il ne s'agisse d'un code très très chaud.
Dans mon test, les meilleures performances sont ENUM> MAP> SWITCH> IF/ELSE IF sous Windows 7.
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
*/
Pour la plupart switch
et la plupart if-then-else
blocs, je ne peux pas imaginer qu’il existe des problèmes de performance appréciables ou importants.
Mais voici la chose: si vous utilisez un bloc switch
, 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'instructions switch
si vous pouvez utiliser un enum
avec des méthodes spécifiques à la constante.
Par rapport à une instruction switch
, une énumération fournit une meilleure sécurité de type et un code plus facile à gérer. Les énumérations peuvent être conçues pour que, si une constante est ajoutée à l'ensemble de constantes, votre code ne soit pas compilé sans fournir une méthode spécifique à la constante pour la nouvelle valeur. D'autre part, oublier d'ajouter un nouveau case
à un bloc switch
ne peut parfois être intercepté au moment de l'exécution que si vous avez la chance d'avoir configuré votre bloc pour qu'il lève une exception.
La performance entre switch
et une méthode spécifique à une constante enum
ne devrait pas être sensiblement différente, mais cette dernière est plus lisible, plus sûre et plus facile à maintenir.