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

Беспокоясь о производительности моего веб-приложения, мне интересно, какое из выражений «если / еще» или switch лучше относительно производительности?

Ответы [ 8 ]

115 голосов
/ 18 января 2010

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

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

См. WM Spec ( переключатель переключения и переключатель таблиц )

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

105 голосов
/ 18 января 2010

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

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

49 голосов
/ 18 января 2010

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

Тем не менее, можно говорить об относительной производительности switch / 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 больше некоторого эмпирического значения, может быть сгенерирована таблица переходов, что очень эффективно.

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

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

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

10 голосов
/ 31 июля 2013

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

Я ненавижу поддерживать блоки 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));
    }
}

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

8 голосов
/ 18 января 2010

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

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

8 голосов
/ 18 января 2010

По словам Клиффа Клика в его выступлении на Java One 2009 года Ускоренный курс по современному оборудованию :

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

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

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

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

4 голосов
/ 19 ноября 2014

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


/*
 * /380562/pochemu-ya-ne-mogu-ispolzovat-operator-switch-dlya-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
 */ 
2 голосов
/ 31 июля 2013

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

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

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

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

...