Турбуючись про ефективність мого веб-додатка, мені цікаво, що з "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));
}
}
switch
es?
Я пам’ятаю, що читав, що в байт-коді 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
константним методом не повинна суттєво відрізнятися, але останній є читабельнішим, безпечнішим та легшим у підтримці.