Турбуючись про ефективність мого веб-додатка, мені цікаво, що з "if / else" або твердження перемикача краще щодо продуктивності?
ifтощо
Турбуючись про ефективність мого веб-додатка, мені цікаво, що з "if / else" або твердження перемикача краще щодо продуктивності?
ifтощо
Відповіді:
Це мікро-оптимізація та передчасна оптимізація, які є злими. Швидше турбуйтеся про читанність та збереження коду, про який йде мова. Якщо if/elseсклеєні більше двох блоків або його розмір непередбачуваний, ви можете дуже розглянути switchтвердження.
Крім того, ви також можете захопити поліморфізм . Спочатку створіть деякий інтерфейс:
public interface Action {
void execute(String input);
}
І в деяких захоплюйтеся всіма реалізаціями Map . Це можна зробити або статично, або динамічно:
Map<String, Action> actions = new HashMap<String, Action>();
Нарешті замініть if/elseабоswitch на щось подібне (залиште тривіальні чеки на зразок нульових покажчиків убік):
actions.get(name).execute(input);
Це може бути мікросповільне ніж if/elseабо switch, але код принаймні набагато краще ремонтувати.
Коли ви говорите про веб-додатки, ви можете використовувати HttpServletRequest#getPathInfo()як ключ дії (врешті-решт, напишіть ще якийсь код, щоб розділити останню частину довідкової інформації в циклі, поки не буде знайдено дію). Тут ви можете знайти подібні відповіді:
Якщо ви турбуєтесь про ефективність веб-застосувань Java EE, то ця стаття також може бути корисною. Є й інші області, які дають набагато більший приріст продуктивності, ніж лише (мікро) оптимізація необробленого коду Java.
Я цілком згоден з думкою, що передчасної оптимізації є чого уникати.
Але це правда, що Java VM має спеціальні байт-коди, які можна використовувати для переключення ().
Див WM Spec ( lookupswitch і tableswitch )
Таким чином, може бути певне підвищення продуктивності, якщо код є частиною графіка CPU продуктивності.
Надзвичайно малоймовірно, що якщо / else або перемикач буде джерелом ваших негараздів. Якщо у вас виникають проблеми з продуктивністю, спершу слід зробити аналіз профілювання продуктивності, щоб визначити, де розташовані повільні точки. Передчасна оптимізація - корінь усього зла!
Тим не менш, можна говорити про відносну продуктивність перемикання проти if / else за допомогою оптимізацій компілятора Java. По-перше, зауважте, що в Java оператори переключення працюють на дуже обмеженому домені - цілих числах. Загалом ви можете переглянути оператор переключення таким чином:
switch (<condition>) {
case c_0: ...
case c_1: ...
...
case c_n: ...
default: ...
}
де c_0, c_1, ..., іc_N є цілими числами , які є мішенями заяви перемикача, і <condition>повинні вирішуватися в вираз цілого.
Якщо цей набір "щільний" - тобто (max (c i ) + 1 - min (c i )) / n> α, де 0 <k <α <1, де kбільше деякого емпіричного значення, a Можна створити таблицю стрибків, яка є високоефективною.
Якщо цей набір не дуже щільний, але n> = β, двійкове дерево пошуку може знайти ціль в O (2 * log (n)), яка також є ефективною.
Для всіх інших випадків оператор переключення настільки ж ефективний, як і еквівалентний ряд операторів if / else. Точні значення α і β залежать від ряду факторів і визначаються модулем оптимізації коду компілятора.
Нарешті, звичайно, якщо домен <condition>не цілих чисел, оператор перемикання є абсолютно марним.
Використовуйте перемикач!
Я ненавиджу підтримувати if-else-блоки! Пройдіть тест:
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));
}
}
switches?
Я пам’ятаю, що читав, що в байт-коді Java є два види операторів Switch. (Я думаю, що це було в «Налаштування продуктивності Java». Один - це дуже швидка реалізація, яка використовує цілі значення оператора перемикання, щоб знати зсув коду, який потрібно виконати. Я здогадуюсь, що використання всіх значень Enum теж потрапить у цю категорію.
Я погоджуюся з багатьма іншими плакатами ... але це може бути передчасно переживати, якщо тільки це не дуже гарячий код.
switchкілька різних способів, деякі ефективніші, ніж інші. Загалом, ефективність буде не гіршою, ніж пряма " ifдрабина", але є досить багато варіацій (особливо з JITC), що важко бути набагато точнішим за це.
За словами Кліффа Кліка в його програмі Java One talk 2009 Crash Course в сучасному обладнанні :
Сьогодні у продуктивності переважають моделі доступу до пам'яті. Переважають пропуски кешу - новий диск - пам'ять. [Слайд 65]
Ви можете отримати його повні слайди тут .
Cliff наводить приклад (закінчення на слайді 30), показуючи, що навіть при процесорі, що робить перейменування реєстру, передбачення гілок та спекулятивне виконання, він може лише запустити 7 операцій за 4 тактових цикла, перш ніж потрібно блокувати через два пропуски кешу, які беруть участь 300 циклів годин для повернення.
Тому він каже, що для прискорення вашої програми слід дивитись не на таку незначну проблему, а на більш великі, такі як ви робите непотрібні перетворення формату даних, наприклад, перетворення "SOAP → XML → DOM → SQL → ... "який" передає всі дані через кеш ".
У моєму тесті кращими показниками є ENUM> MAP> SWITCH> IF / ELSE IF у 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
У більшості switchта більшості if-then-elseблоків я не можу уявити, що є якісь помітні чи значні проблеми, пов’язані з ефективністю.
Але ось у чому річ: якщо ви використовуєте switchблок, саме його використання говорить про те, що ви перемикаєте значення, взяті з набору констант, відомих під час компіляції. У цьому випадку ви дійсно не повинні використовувати switchоператори, якщо ви можете використовуватиenum методи з постійними специфічними методами.
Порівняно з switchтвердженням, enum забезпечує кращу безпеку типу та код, який легше підтримувати. Енуми можуть бути сконструйовані таким чином, що якщо до набору констант додається константа, ваш код не буде компілюватися, не надаючи постійному конкретному методу для нового значення. З іншого боку, забувши додати нове caseдо switchблоку, іноді можна потрапити лише під час запуску, якщо вам пощастило встановити блок для викидання виключення.
Продуктивність між методом switchта enumконстантним методом не повинна суттєво відрізнятися, але останній є читабельнішим, безпечнішим та легшим у підтримці.