Ich mache mir Sorgen um die Leistung meiner Webanwendung und frage mich, welche der "if / else" - oder switch-Anweisungen hinsichtlich der Leistung besser ist.
if
usw. verwendet werden.
Ich mache mir Sorgen um die Leistung meiner Webanwendung und frage mich, welche der "if / else" - oder switch-Anweisungen hinsichtlich der Leistung besser ist.
if
usw. verwendet werden.
Antworten:
Das sind Mikrooptimierung und vorzeitige Optimierung, die böse sind. Sorgen Sie sich lieber um die Lesbarkeit und Wartbarkeit des betreffenden Codes. Wenn mehr als zwei if/else
Blöcke zusammengeklebt sind oder ihre Größe nicht vorhersehbar ist, können Sie eine switch
Aussage in Betracht ziehen .
Alternativ können Sie auch Polymorphismus greifen . Erstellen Sie zuerst eine Schnittstelle:
public interface Action {
void execute(String input);
}
Und in einigen Fällen alle Implementierungen in den Griff bekommen Map
. Sie können dies entweder statisch oder dynamisch tun:
Map<String, Action> actions = new HashMap<String, Action>();
Ersetzen Sie schließlich das if/else
oder switch
durch etwas Ähnliches (lassen Sie triviale Überprüfungen wie Nullzeiger beiseite):
actions.get(name).execute(input);
Es ist möglicherweise mikroslower als if/else
oder switch
, aber der Code ist zumindest weitaus besser zu warten.
Wenn Sie über Webanwendungen sprechen, können Sie diese HttpServletRequest#getPathInfo()
als Aktionsschlüssel verwenden (schreiben Sie eventuell etwas mehr Code, um den letzten Teil von pathinfo in einer Schleife aufzuteilen, bis eine Aktion gefunden wird). Ähnliche Antworten finden Sie hier:
Wenn Sie sich allgemein Gedanken über die Leistung der Java EE-Webanwendung machen, ist dieser Artikel möglicherweise ebenfalls hilfreich. Es gibt andere Bereiche, die einen viel größeren Leistungsgewinn bieten als nur die (Mikro-) Optimierung des Java-Rohcodes.
Ich stimme voll und ganz der Meinung zu, dass eine vorzeitige Optimierung vermieden werden sollte.
Es stimmt jedoch, dass die Java-VM über spezielle Bytecodes verfügt, die für switch () verwendet werden können.
Siehe WM- Spezifikation ( Lookupswitch und Tableswitch )
Es kann also zu Leistungssteigerungen kommen, wenn der Code Teil des Leistungs-CPU-Diagramms ist.
Es ist äußerst unwahrscheinlich, dass ein if / else oder ein Switch die Ursache für Ihre Leistungsprobleme sein wird. Wenn Sie Leistungsprobleme haben, sollten Sie zuerst eine Leistungsprofilanalyse durchführen, um festzustellen, wo sich die langsamen Stellen befinden. Vorzeitige Optimierung ist die Wurzel allen Übels!
Trotzdem ist es möglich, mit den Java-Compiler-Optimierungen über die relative Leistung von Switch im Vergleich zu if / else zu sprechen. Beachten Sie zunächst, dass switch-Anweisungen in Java mit einer sehr begrenzten Domäne arbeiten - Ganzzahlen. Im Allgemeinen können Sie eine switch-Anweisung wie folgt anzeigen:
switch (<condition>) {
case c_0: ...
case c_1: ...
...
case c_n: ...
default: ...
}
wobei c_0
, c_1
, ... und c_N
sind ganze Zahlen , die Ziele der switch - Anweisung sind, und <condition>
müssen auf eine ganze Zahl Ausdruck lösen.
Wenn diese Menge "dicht" ist, dh (max (c i ) + 1 - min (c i )) / n> α, wobei 0 <k <α <1, wobei k
größer als ein empirischer Wert ist, a Es kann eine Sprungtabelle erzeugt werden, die sehr effizient ist.
Wenn diese Menge nicht sehr dicht ist, aber n> = β, kann ein binärer Suchbaum das Ziel in O (2 * log (n)) finden, was ebenfalls noch effizient ist.
In allen anderen Fällen ist eine switch-Anweisung genauso effizient wie die entsprechende Reihe von if / else-Anweisungen. Die genauen Werte von α und β hängen von einer Reihe von Faktoren ab und werden vom Codeoptimierungsmodul des Compilers bestimmt.
Schließlich <condition>
ist eine switch-Anweisung natürlich völlig nutzlos , wenn die Domäne von nicht die ganzen Zahlen sind.
Schalter benutzen!
Ich hasse es, wenn-sonst-Blöcke zu pflegen! Habe einen 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
?
Ich erinnere mich, dass ich gelesen habe, dass es im Java-Bytecode zwei Arten von Switch-Anweisungen gibt. (Ich denke, es war in 'Java Performance Tuning'. Eine ist eine sehr schnelle Implementierung, die die Ganzzahlwerte der switch-Anweisung verwendet, um den Offset des auszuführenden Codes zu kennen. Dies würde erfordern, dass alle Ganzzahlen aufeinanderfolgend und in einem genau definierten Bereich sind Ich vermute, dass die Verwendung aller Werte einer Aufzählung auch in diese Kategorie fallen würde.
Ich stimme jedoch vielen anderen Postern zu ... es kann verfrüht sein, sich darüber Sorgen zu machen, es sei denn, dies ist ein sehr, sehr heißer Code.
switch
verschiedene Methoden, von denen einige effizienter sind als andere. Im Allgemeinen ist die Effizienz nicht schlechter als bei einer einfachen " if
Leiter", aber es gibt genügend Variationen (insbesondere beim JITC), so dass es schwierig ist, viel genauer zu sein.
Laut Cliff Click in seinem Java One-Vortrag 2009 Ein Crashkurs in moderner Hardware :
Heutzutage wird die Leistung von Mustern des Speicherzugriffs dominiert. Cache-Fehler dominieren - Speicher ist die neue Festplatte. [Folie 65]
Sie können seine vollständigen Folien hier erhalten .
Cliff gibt ein Beispiel (Abschluss auf Folie 30), das zeigt, dass selbst wenn die CPU Registerumbenennung, Verzweigungsvorhersage und spekulative Ausführung ausführt, sie nur 7 Operationen in 4 Taktzyklen starten kann, bevor sie aufgrund von zwei Cache-Fehlern blockieren muss 300 Taktzyklen, um zurückzukehren.
Um Ihr Programm zu beschleunigen, sollten Sie sich nicht mit solchen kleinen Problemen befassen, sondern mit größeren, z. B. ob Sie unnötige Datenformatkonvertierungen vornehmen, z. B. "SOAP → XML → DOM → SQL → ...". "welches" alle Daten durch den Cache leitet ".
In meinem Test ist die bessere Leistung unter Windows 7 ENUM> MAP> SWITCH> IF / ELSE IF .
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
Für die meisten switch
und die meisten if-then-else
Blöcke kann ich mir nicht vorstellen, dass es nennenswerte oder signifikante leistungsbezogene Bedenken gibt.
Aber hier ist die Sache: Wenn Sie einen switch
Block verwenden, deutet seine Verwendung darauf hin, dass Sie einen Wert aktivieren, der aus einer Reihe von Konstanten stammt, die zur Kompilierungszeit bekannt sind. In diesem Fall sollten Sie wirklich überhaupt keine switch
Anweisungen verwenden, wenn Sie eine verwenden könnenenum
mit konstanten spezifischen Methoden verwenden können.
Im Vergleich zu einer switch
Anweisung bietet eine Aufzählung eine bessere Typensicherheit und einen Code, der einfacher zu warten ist. Aufzählungen können so gestaltet werden, dass Ihr Code nicht kompiliert wird, wenn eine Konstante zum Satz von Konstanten hinzugefügt wird, ohne eine konstantenspezifische Methode für den neuen Wert bereitzustellen. Andererseits kann das Vergessen, case
einem switch
Block einen neuen hinzuzufügen, manchmal nur zur Laufzeit abgefangen werden, wenn Sie das Glück haben, Ihren Block so eingerichtet zu haben, dass eine Ausnahme ausgelöst wird.
Die Leistung zwischen switch
und einer enum
konstantenspezifischen Methode sollte sich nicht wesentlich unterscheiden. Letztere ist jedoch besser lesbar, sicherer und einfacher zu warten.