Martwiąc się o wydajność mojej aplikacji internetowej, zastanawiam się, która z instrukcji „if / else” lub switch jest lepsza pod względem wydajności?
if
itp.
Martwiąc się o wydajność mojej aplikacji internetowej, zastanawiam się, która z instrukcji „if / else” lub switch jest lepsza pod względem wydajności?
if
itp.
Odpowiedzi:
To jest mikro optymalizacja i przedwczesna optymalizacja, które są złe. Raczej martw się o czytelność i łatwość utrzymania danego kodu. Jeśli jest więcej niż dwa if/else
sklejone ze sobą bloki lub jego rozmiar jest nieprzewidywalny, możesz bardzo rozważyć switch
stwierdzenie.
Alternatywnie możesz też pobrać Polimorfizm . Najpierw utwórz interfejs:
public interface Action {
void execute(String input);
}
I zdobądź wszystkie implementacje w niektórych Map
. Możesz to zrobić statycznie lub dynamicznie:
Map<String, Action> actions = new HashMap<String, Action>();
Na koniec zamień if/else
lub switch
na coś takiego (pozostawiając na boku trywialne sprawdzenia, takie jak nullpointers):
actions.get(name).execute(input);
To może być microslower niż if/else
albo switch
, ale kod jest co najmniej o wiele lepsze w utrzymaniu.
Jeśli mówisz o aplikacjach internetowych, możesz użyć go HttpServletRequest#getPathInfo()
jako klawisza akcji (ostatecznie napisz trochę więcej kodu, aby podzielić ostatnią część pathinfo w pętli, aż zostanie znaleziona akcja). Znajdziesz tutaj podobne odpowiedzi:
Jeśli martwisz się o ogólną wydajność aplikacji internetowej Java EE, ten artykuł może Ci się również przydać. Są inne obszary, które dają znacznie większy wzrost wydajności niż tylko (mikro) optymalizacja surowego kodu Java.
Całkowicie zgadzam się z opinią, że przedwczesna optymalizacja jest czymś, czego należy unikać.
Ale prawdą jest, że maszyna wirtualna Java ma specjalne kody bajtowe, których można użyć do przełączania ().
Zobacz specyfikację WM ( przełącznik wyszukiwania i przełącznik tabel )
Więc może wystąpić pewien wzrost wydajności, jeśli kod jest częścią wykresu wydajności procesora.
Jest bardzo mało prawdopodobne, aby przełącznik if / else lub przełącznik był źródłem problemów z wydajnością. Jeśli masz problemy z wydajnością, najpierw wykonaj analizę profilowania wydajności, aby określić, gdzie są wolne miejsca. Przedwczesna optymalizacja jest źródłem wszelkiego zła!
Niemniej jednak, można mówić o względnej wydajności przełącznika w porównaniu z if / else z optymalizacjami kompilatora Java. Po pierwsze, zauważ, że w Javie instrukcje switch działają na bardzo ograniczonej domenie - liczbach całkowitych. Zasadniczo instrukcję switch można wyświetlić w następujący sposób:
switch (<condition>) {
case c_0: ...
case c_1: ...
...
case c_n: ...
default: ...
}
gdzie c_0
, c_1
, ..., i c_N
są liczbami, które są integralnymi cele instrukcji switch i <condition>
musi rozwiązać, aby wyraz całkowitej.
Jeśli ten zbiór jest „gęsty” - to znaczy (max (c i ) + 1 - min (c i )) / n> α, gdzie 0 <k <α <1, gdzie k
jest większy niż pewna wartość empiryczna, a można wygenerować tablicę skoków, co jest bardzo wydajne.
Jeśli ten zestaw nie jest bardzo gęsty, ale n> = β, binarne drzewo wyszukiwania może znaleźć cel w O (2 * log (n)), co również jest wydajne.
We wszystkich innych przypadkach instrukcja switch jest dokładnie tak samo wydajna, jak równoważna seria instrukcji if / else. Dokładne wartości α i β zależą od wielu czynników i są określane przez moduł optymalizacji kodu kompilatora.
Wreszcie, oczywiście, jeśli dziedziną <condition>
nie są liczby całkowite, instrukcja switch jest całkowicie bezużyteczna.
Użyj przełącznika!
Nienawidzę utrzymywać blokad if-else-block! Zrób 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?
Pamiętam, że czytałem, że w kodzie bajtowym Javy istnieją 2 rodzaje instrukcji Switch. (Myślę, że było to w 'Java Performance Tuning' Jedna z nich to bardzo szybka implementacja, która używa wartości całkowitych instrukcji switch, aby poznać przesunięcie kodu do wykonania. Wymagałoby to, aby wszystkie liczby całkowite były następujące po sobie iw dobrze zdefiniowanym zakresie , Domyślam się, że użycie wszystkich wartości Enum również należałoby do tej kategorii.
Zgadzam się jednak z wieloma innymi plakatami ... martwienie się o to może być przedwczesne, chyba że jest to bardzo gorący kod.
switch
kilka różnych sposobów, niektóre bardziej wydajne niż inne. Ogólnie rzecz biorąc, wydajność nie będzie gorsza niż prosta „ if
drabinka”, ale jest wystarczająco dużo odmian (zwłaszcza w przypadku JITC), że trudno jest być o wiele bardziej precyzyjnym.
Według Cliffa Click'a w jego wykładzie Java One z 2009 roku A Crash Course in Modern Hardware :
Obecnie wydajność jest zdominowana przez wzorce dostępu do pamięci. Dominują chybienia w pamięci podręcznej - pamięć jest nowym dyskiem. [Slajd 65]
Można uzyskać pełnię slajdy tutaj .
Cliff podaje przykład (kończąc na slajdzie 30) pokazujący, że nawet jeśli procesor wykonuje zmianę nazwy rejestru, przewidywanie gałęzi i wykonanie spekulacyjne, jest w stanie rozpocząć tylko 7 operacji w 4 cyklach zegara, zanim będzie musiał zablokować się z powodu dwóch chybień w pamięci podręcznej, które wymagają 300 cykli zegara do powrotu.
Dlatego mówi, że aby przyspieszyć program, nie powinieneś zajmować się tego rodzaju drobnymi problemami, ale większymi, takimi jak to, czy wykonujesz niepotrzebne konwersje formatu danych, takie jak konwersja „SOAP → XML → DOM → SQL →… „który” przekazuje wszystkie dane przez pamięć podręczną ”.
W moim teście lepsza wydajność to ENUM> MAP> SWITCH> IF / ELSE IF w 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
W przypadku większości switch
i większości if-then-else
bloków nie mogę sobie wyobrazić, że są jakieś zauważalne lub znaczące problemy związane z wydajnością.
Ale o to chodzi: jeśli używasz switch
bloku, samo jego użycie sugeruje, że włączasz wartość pobraną z zestawu stałych znanych w czasie kompilacji. W takim przypadku naprawdę nie powinieneś w ogóle używać switch
instrukcji, jeśli możesz użyć enum
metody z metodami specyficznymi dla stałej.
W porównaniu z switch
instrukcją wyliczenie zapewnia lepsze bezpieczeństwo typów i kod, który jest łatwiejszy w utrzymaniu. Wyliczenia można zaprojektować w taki sposób, aby po dodaniu stałej do zestawu stałych kod nie był kompilowany bez zapewnienia metody specyficznej dla stałej dla nowej wartości. Z drugiej strony zapomnienie o dodaniu nowego case
do switch
bloku może czasami zostać złapane tylko w czasie wykonywania, jeśli masz szczęście, że ustawiłeś blok tak, aby zgłosił wyjątek.
Wydajność pomiędzy switch
i enum
metodą specyficzną dla stałej nie powinna się znacząco różnić, ale ta druga jest bardziej czytelna, bezpieczniejsza i łatwiejsza w utrzymaniu.