Яка відносна різниця продуктивності оператора if / else проти перемикача на Java?


122

Турбуючись про ефективність мого веб-додатка, мені цікаво, що з "if / else" або твердження перемикача краще щодо продуктивності?


6
Чи є у вас якісь причини вважати, що однаковий байт-код не створюється для двох конструкцій?
Паскаль Куок

2
@Pascal: оптимізація може бути здійснена за допомогою пошуку таблиць замість списку ifтощо
jldupont

18
"Передчасна оптимізація - корінь усього зла" - Дональд Кнут
зниклийфактор

104
Хоча це, безумовно, передчасна оптимізація, "Бездумне дотримання цитати, винесеної погано з контексту, є причиною, що нам потрібен багатоядерний комп'ютер високого класу лише для відображення досить чуйного графічного інтерфейсу сьогодні" - Мені.
Лоуренс Дол

2
Кнут має точний розум. Зверніть увагу, що класифікатор "передчасний". Оптимізація - цілком поважна проблема. Однак, сервер пов'язаний з підключенням до IO, і вузькі місця мережевого та дискового вводу-виводу є на порядок більш значущим, ніж усе, що у вас на сервері.
alphazero

Відповіді:


108

Це мікро-оптимізація та передчасна оптимізація, які є злими. Швидше турбуйтеся про читанність та збереження коду, про який йде мова. Якщо 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.


1
або розглянути замість цього поліморфізм
jk.

Це дійсно більше рекомендується у випадку "непередбачуваної" кількості блоків if / else.
BalusC

73
Я не так швидко відкидаю всю ранню оптимізацію, як "злу". Бути занадто агресивним є нерозумним, але якщо стикатися з конструкціями порівнянної читабельності, вибір одного, який, як відомо, виконує кращі результати, є відповідним рішенням.
Брайан Кноблауш

8
Версія пошуку HashMap легко може бути в 10 разів повільніше порівняно з інструкцією tablewitsch. Я б не назвав це "мікросповільницею"!
x4u

7
Мені цікаво насправді знати внутрішню роботу Java в загальному випадку із заявами про переключення - мене не цікавить, чи хтось вважає, що це пов’язано із надто пріоритетним ранньою оптимізацією. Коли я говорив, я абсолютно не маю уявлення, чому ця відповідь настільки обґрунтована і чому це прийнята відповідь ... це нічого не дає відповіді на початкове запитання.
searchchengine27

125

Я цілком згоден з думкою, що передчасної оптимізації є чого уникати.

Але це правда, що Java VM має спеціальні байт-коди, які можна використовувати для переключення ().

Див WM Spec ( lookupswitch і tableswitch )

Таким чином, може бути певне підвищення продуктивності, якщо код є частиною графіка CPU продуктивності.


60
Цікаво, чому цей коментар не оцінюється вище: він є найбільш інформативним з усіх. Я маю на увазі: всі ми вже знаємо, що передчасна оптимізація є поганою і такою, не потрібно пояснювати це в 1000 разів.
Фолкерт ван Хейсден

5
+1 Щодо stackoverflow.com/a/15621602/89818, схоже, підвищення продуктивності дійсно є, і ви повинні побачити перевагу, якщо ви використовуєте 18+ випадків.
каре

52

Надзвичайно малоймовірно, що якщо / 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>не цілих чисел, оператор перемикання є абсолютно марним.


+1. Є хороший шанс, що час, витрачений на мережевий ввод-вивід, легко затьмарить цю конкретну проблему.
Адам Пейнтер

3
Слід зазначити, що комутатори працюють з більш ніж просто вбудованими. З навчальних посібників Java: "Перемикач працює з примітними типами байт, коротких, знаків та int. Це також працює з переліченими типами (обговорюється в типах Enum), класом String та декількома спеціальними класами, які містять певні примітивні типи : Характер, Байт, Короткий і Цілий (обговорюється в цифрах і рядках). " Підтримка String - це нещодавнє доповнення; додано в Java 7. docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html
atraudes

1
@jhonFeminella Чи можете ви порівняти ефекти BIG O для струни Java7 у Swtich порівняно з String у if / else if ..?
Kanagavelu Sugumar

Точніше, javac 8 надає вагу від 3 до часу складності над просторовою складністю: stackoverflow.com/a/31032054/895245
Чіро Сантіллі郝海东冠状病六四事件法轮功

11

Використовуйте перемикач!

Я ненавиджу підтримувати 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));
    }
}

Мій стандартний код C # для бенчмаркінгу


Не могли б ви десь детальніше розібратися, як ви це зробили?
DerMike

Дуже дякую за ваше оновлення. Я маю на увазі, вони різняться на один порядок - що, звичайно, можливо. Ви впевнені, що компілятор не просто оптимізував switches?
DerMike

@DerMike Я не пам'ятаю, як я отримав старі результати. Я сьогодні дуже різний. Але спробуйте самі і дайте мені знати, як це виходить.
Bitterblue

1
коли я запускаю його на своєму ноутбуці; потрібен час перемикання: 3585, якщо / ще потрібен час: 3458, тож якщо / ще краще :) чи не гірше
халіл

1
Домінуюча вартість у тесті - це генерація випадкових чисел. Я змінив тест, щоб генерувати випадкове число перед циклом, і використовував значення temp, щоб повернутись до r. Тоді перемикач майже вдвічі швидший, ніж ланцюг "інше".
костілл

8

Я пам’ятаю, що читав, що в байт-коді Java є два види операторів Switch. (Я думаю, що це було в «Налаштування продуктивності Java». Один - це дуже швидка реалізація, яка використовує цілі значення оператора перемикання, щоб знати зсув коду, який потрібно виконати. Я здогадуюсь, що використання всіх значень Enum теж потрапить у цю категорію.

Я погоджуюся з багатьма іншими плакатами ... але це може бути передчасно переживати, якщо тільки це не дуже гарячий код.


4
+1 для коментаря із гарячим кодом. Якщо його в головному циклі, це не передчасно.
KingAndrew

Так, javac реалізує switchкілька різних способів, деякі ефективніші, ніж інші. Загалом, ефективність буде не гіршою, ніж пряма " ifдрабина", але є досить багато варіацій (особливо з JITC), що важко бути набагато точнішим за це.
Гарячі лизання

8

За словами Кліффа Кліка в його програмі Java One talk 2009 Crash Course в сучасному обладнанні :

Сьогодні у продуктивності переважають моделі доступу до пам'яті. Переважають пропуски кешу - новий диск - пам'ять. [Слайд 65]

Ви можете отримати його повні слайди тут .

Cliff наводить приклад (закінчення на слайді 30), показуючи, що навіть при процесорі, що робить перейменування реєстру, передбачення гілок та спекулятивне виконання, він може лише запустити 7 операцій за 4 тактових цикла, перш ніж потрібно блокувати через два пропуски кешу, які беруть участь 300 циклів годин для повернення.

Тому він каже, що для прискорення вашої програми слід дивитись не на таку незначну проблему, а на більш великі, такі як ви робите непотрібні перетворення формату даних, наприклад, перетворення "SOAP → XML → DOM → SQL → ... "який" передає всі дані через кеш ".


4

У моєму тесті кращими показниками є 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
халіл

@halil Я не впевнений, як працює цей код у різних середовищах, але ви згадали, чи / elseif краще, ніж Switch і Map, що я не в змозі переконати, оскільки якщо / elseif повинен виконувати більше, не більше разів дорівнює порівнянню.
Канагавелу Сугумар

2

У більшості switchта більшості if-then-elseблоків я не можу уявити, що є якісь помітні чи значні проблеми, пов’язані з ефективністю.

Але ось у чому річ: якщо ви використовуєте switchблок, саме його використання говорить про те, що ви перемикаєте значення, взяті з набору констант, відомих під час компіляції. У цьому випадку ви дійсно не повинні використовувати switchоператори, якщо ви можете використовуватиenum методи з постійними специфічними методами.

Порівняно з switchтвердженням, enum забезпечує кращу безпеку типу та код, який легше підтримувати. Енуми можуть бути сконструйовані таким чином, що якщо до набору констант додається константа, ваш код не буде компілюватися, не надаючи постійному конкретному методу для нового значення. З іншого боку, забувши додати нове caseдо switchблоку, іноді можна потрапити лише під час запуску, якщо вам пощастило встановити блок для викидання виключення.

Продуктивність між методом switchта enumконстантним методом не повинна суттєво відрізнятися, але останній є читабельнішим, безпечнішим та легшим у підтримці.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.