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

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

8 ответов


это микро-оптимизация и преждевременная оптимизация, которые являются злом. А беспокоиться о readabililty и ремонтопригодность код в вопрос. Если их больше двух if/else блоки склеены вместе или его размер непредсказуем, то вы можете высоко рассмотреть switch заявление.

кроме того, вы также можете захватить полиморфизм. Сначала создайте некоторый интерфейс:

public interface Action { 
    void execute(String input);
}

и получить все реализации в некоторых Map. Вы может делать это статически или динамически:

Map<String, Action> actions = new HashMap<String, Action>();

наконец-то заменить if/else или switch чем-то вроде этого (оставляя в стороне тривиальные проверки, такие как nullpointers):

actions.get(name).execute(input);

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

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

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


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

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

посмотреть WM Spec (lookupswitch и tableswitch)

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


крайне маловероятно, что 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я) + 1-мин (cя)) / n > α, где 0 k больше, чем некоторое эмпирическое значение, таблица перехода смогите быть произведено, которое высоки-эффективно.

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

для всех других случаев оператор switch точно так же эффективен, как эквивалентная серия операторов 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));
    }
}

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


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

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


согласно Клифф клик в его 2009 Java One talk ускоренный курс современного оборудования:

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

вы можете получить его полный скольжения здесь.

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

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


в моем тесте лучшая производительность ПЕРЕЧИСЛЕНИЕ > КАРТА > ПЕРЕКЛЮЧАТЕЛЬ > 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
 */ 

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

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

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

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