Какова относительная разница в производительности if/else по сравнению с оператором switch в Java?

Беспокойство о моих действиях в веб-приложении, мне интересно, какой из "if/else" или оператор switch лучше относится к производительности?

Ответ 1

Эта микро оптимизация и преждевременная оптимизация, которые являются злыми. Скорее беспокоиться о читабельности и ремонтопригодности кода. Если имеется более двух блоков 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);

Это может быть miclowlower, чем if/else или switch, но код, по крайней мере, намного лучше обслуживается.

Как вы говорите о веб-приложениях, вы можете использовать HttpServletRequest#getPathInfo() в качестве ключа действия (в конце концов напишите еще один код, чтобы разделить последнюю часть путиinfo в цикле до тех пор, пока не будет найдено действие). Вы можете найти здесь похожие ответы:

Если вы беспокоитесь о производительности веб-приложения Java EE в целом, то вы можете найти эту статью. Существуют и другие области, которые дают производительность гораздо больше, чем только (микро) оптимизация необработанного кода Java.

Ответ 2

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

Но верно, что Java VM имеет специальные байт-коды, которые могут быть использованы для switch().

См. WM Spec (lookupswitch и tableswitch)

Таким образом, может быть некоторый прирост производительности, если код является частью графика производительности.

Ответ 3

Чрезвычайно маловероятно, что if/else или переключатель будет источником ваших проблем с производительностью. Если у вас проблемы с производительностью, сначала необходимо проанализировать профилирование производительности, чтобы определить, где находятся медленные точки. Преждевременная оптимизация - это корень всего зла!

Тем не менее, можно говорить об относительной производительности коммутатора vs. if/else с оптимизацией компилятора Java. Прежде всего отметим, что в Java операторы switch работают с очень ограниченными целыми числами домена. В общем случае вы можете просмотреть оператор switch следующим образом:

switch (<condition>) {
   case c_0: ...
   case c_1: ...
   ...
   case c_n: ...
   default: ...
}

где c_0, c_1,... и c_N являются целыми числами, являющимися мишенями оператора switch, а <condition> должны решаться на целочисленное выражение.

  • Если этот набор является "плотным", то есть (max (c i) + 1 - min (c i))/n > & alpha;, где 0 < k < &альфа; < 1, где k больше некоторого эмпирического значения, может быть сгенерирована таблица переходов, которая является высокоэффективной.

  • Если это множество не очень плотно, но n >= & beta;, двоичное дерево поиска может найти цель в O (2 * log (n)), которая также эффективна.

Для всех остальных случаев оператор switch точно так же эффективен, как эквивалентная серия операторов if/else. Точные значения & alpha; и & beta; зависят от ряда факторов и определяются модулем оптимизации кода компилятора.

Наконец, конечно, если область <condition> не является целым числом, переключатель утверждение совершенно бесполезно.

Ответ 4

Согласно Cliff Click в своем разговоре 2009 года Java One Курс Crash в современном оборудовании:

Сегодня в производительности преобладают шаблоны доступа к памяти. Недостатки кэша доминируют - память - это новый диск. [Слайд 65]

Вы можете получить его полные слайды здесь.

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

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

Ответ 5

Использовать переключатель!

Мне не нравится поддерживать if-else-blocks! Проведите тест:

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));
    }
}

Стандартный код для С# для сравнения >

Ответ 6

Я помню, что читал, что в байт-коде Java есть 2 вида операторов Switch. (Я думаю, что это было в "Настройке производительности Java" One - очень быстрая реализация, которая использует значения целочисленных операторов switch, чтобы знать смещение выполняемого кода. Это потребует, чтобы все целые числа были последовательными и в четко определенном диапазоне Я предполагаю, что использование всех значений Enum также попадет в эту категорию.

Я согласен со многими другими плакатами, хотя... может быть преждевременно беспокоиться об этом, если это не очень-очень горячий код.

Ответ 7

В моем тесте лучшая производительность 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
 */ 

Ответ 8

Для большинства блоков switch и most if-then-else я не могу себе представить, что есть какие-то заметные или существенные проблемы, связанные с производительностью.

Но вот что: если вы используете блок switch, его использование позволяет предположить, что вы включаете значение, взятое из набора констант, известных во время компиляции. В этом случае вы действительно не должны использовать операторы switch вообще, если вы можете использовать enum с помощью методов, специфичных для констант.

По сравнению с оператором switch, перечисление обеспечивает лучшую безопасность и код типа, который легче поддерживать. Перечисления могут быть сконструированы так, что если константа добавляется к набору констант, ваш код не будет компилироваться, не предоставляя постоянный метод для нового значения. С другой стороны, забыть добавить новый блок case в switch иногда может быть пойман только во время выполнения, если вам посчастливилось установить блокировку для исключения.

Производительность между switch и enum постоянным параметром не должна быть существенно различной, но последняя более читаема, более безопасна и удобна в обслуживании.