Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java: Общие вопросы > Что быстрее if'ы или switch'и?


Автор: Royan 1.9.2009, 13:44
Держу пари на 5 золотых, что никто не проверял это на практике smile Я из чистого любопытства написал, простенький тест, и чтобы он был более объективен, хотел бы попросить всех желающих прогнать его у себя на машине.
Код теста:
Код

package test;

import java.util.Random;

public class Test {

    final static int LEN = 100000000;

    final static int SET = 100;

    private static long totalTimeDefinedIf = 0;
    private static long totalTimeRandomIf = 0;
    private static long totalTimeDefinedSwitch = 0;
    private static long totalTimeRandomSwitch = 0;

    public static void main(String[] args) {
        Random r = new Random(System.currentTimeMillis());
        int[] randomArr = new int[LEN];
        for (int i = 0; i < randomArr.length; i++) {
            randomArr[i] = r.nextInt(10);
        }

        

        for (int i = 0; i < SET; ++i) {
            System.err.println(String.format("Percent done: %.0f%%", (double)i/SET * 100));
            iterateIfs(randomArr, r);
            iterateSwitches(randomArr, r);
        }
        
        
        System.err.println("DefinedIf,RandomIf,DefinedSw,RandomSw");
        
        System.err.println(String.format("%.2f,%.2f,%.2f,%.2f",
                (double) totalTimeDefinedIf / SET, 
                (double) totalTimeRandomIf / SET, 
                (double) totalTimeDefinedSwitch / SET,
                (double) totalTimeRandomSwitch / SET)
        );
    }

    private static void iterateIfs(final int[] randomArr, final Random r) {
        final long start = System.currentTimeMillis();
        for (int i = 0; i < randomArr.length; i++) {
            int temp = -1;
            if (0 == randomArr[i]) {
                temp = 0;
            } else if (1 == randomArr[i]) {
                temp = 1;
            } else if (2 == randomArr[i]) {
                temp = 2;
            } else if (3 == randomArr[i]) {
                temp = 3;
            } else if (4 == randomArr[i]) {
                temp = 4;
            } else if (5 == randomArr[i]) {
                temp = 5;
            } else if (6 == randomArr[i]) {
                temp = 6;
            } else if (7 == randomArr[i]) {
                temp = 7;
            } else if (8 == randomArr[i]) {
                temp = 8;
            } else if (9 == randomArr[i]) {
                temp = 9;
            } else {
                temp = randomArr[i];
                System.err.println("Err: " + temp);
            }
        }

        final long switchStart = System.currentTimeMillis() - start;
        totalTimeDefinedIf += switchStart;

        
        
        
        
        final long randomStart = System.currentTimeMillis();
        for (int i = 0; i < randomArr.length; i++) {
            final int rnd = r.nextInt(10);
            int temp = -1;
            if (0 == rnd) {
                temp = 0;
            } else if (1 == rnd) {
                temp = 1;
            } else if (2 == rnd) {
                temp = 2;
            } else if (3 == rnd) {
                temp = 3;
            } else if (4 == rnd) {
                temp = 4;
            } else if (5 == rnd) {
                temp = 5;
            } else if (6 == rnd) {
                temp = 6;
            } else if (7 == rnd) {
                temp = 7;
            } else if (8 == rnd) {
                temp = 8;
            } else if (9 == rnd) {
                temp = 9;
            } else {
                temp = rnd;
                System.err.println("Err: " + temp);
            }
        }
        
        final long endRndIfStart = System.currentTimeMillis()
        - randomStart;

        totalTimeRandomIf += endRndIfStart;

    }

    private static void iterateSwitches(final int[] randomArr, final Random r) {

        final long switchStart = System.currentTimeMillis();
        for (int i = 0; i < randomArr.length; i++) {
            int temp = -1;
            switch (randomArr[i]) {
            case 0:
                temp = 0;
                break;
            case 1:
                temp = 1;
                break;
            case 2:
                temp = 2;
                break;
            case 3:
                temp = 3;
                break;
            case 4:
                temp = 4;
                break;
            case 5:
                temp = 5;
                break;
            case 6:
                temp = 6;
                break;
            case 7:
                temp = 7;
                break;
            case 8:
                temp = 8;
                break;
            case 9:
                temp = 9;
                break;
            default:
                temp = randomArr[i];
                System.err.println("Err: " + temp);
            }
        }
        
        
        
        

        final long endSwitchStart = System.currentTimeMillis() - switchStart;
        totalTimeDefinedSwitch += endSwitchStart;

        final long rndSwitchStart = System.currentTimeMillis();
        for (int i = 0; i < randomArr.length; i++) {
            final int rnd = r.nextInt(10);
            int temp = -1;
            switch (rnd) {
            case 0:
                temp = 0;
                break;
            case 1:
                temp = 1;
                break;
            case 2:
                temp = 2;
                break;
            case 3:
                temp = 3;
                break;
            case 4:
                temp = 4;
                break;
            case 5:
                temp = 5;
                break;
            case 6:
                temp = 6;
                break;
            case 7:
                temp = 7;
                break;
            case 8:
                temp = 8;
                break;
            case 9:
                temp = 9;
                break;
            default:
                temp = rnd;
                System.err.println("Err: " + temp);
            }
        }

        final long endRndSwitchStart = System.currentTimeMillis()
                - rndSwitchStart;
        totalTimeRandomSwitch += endRndSwitchStart;
    }
}


Тест идет около 15 минут, поэтому просьба проявить терпение. В результатах присутствует 4 метрики
  •  DefinedIf -- Среднее время работы условий на массиве 
  •  RandomIf -- Среднее время работы условий на случайных данных
  •  DefinedSw -- Среднее время работы switch на массиве 
  •  RandomSw -- Среднее время работы switch на случайных данных
 

Случайные данные используются для того, чтобы немного спутать Advanced Branch Prediction

В ответах просьба указать выхлоп, ось и ваш CPU

Мои результаты
Java: Java™ SE Runtime Environment (build 1.6.0_14-b08) Java HotSpot™ Server VM (build 14.0-b16, mixed mode)
OS: Ubuntu 9.04
CPU: Core™2 Duo CPU     P8600  @ 2.40GHz
Цитата

DefinedIf,RandomIf,DefinedSw,RandomSw
1253.61,3060.33,1198.82,3190.20


Автор: Hidrag 1.9.2009, 14:44
как то раз я декомпилировал свой код (потерял исходники) так в нем увидел что при компиляции мои ифы были заменены на свитчи, и многие циклы были изменены на while(true) с условием на брик внутри

Автор: LSD 1.9.2009, 14:48
Цитата
Что быстрее if'ы или switch'и?

Ну учитывая наличие достаточно неплохого JIT, не думаю что там будет какая-то существенная разница.

Автор: Royan 1.9.2009, 15:19
LSD, Ну речь о разнице в 4,5% на моем примере. Положим, что в приложении данные конструкции используются в каком-нибудь высоконагруженном месте, тогда эти 4,5% весьма интересны

Автор: revenforv 1.9.2009, 16:44
Вообще-то тест несостоятелен по двум причинам:
- он искусственный;
- вы используете массив int, операции над которыми в jvm хорошо-оптимизированы.

И еще.. сравнивать if с switch - то же самое что сравнивать сладкое и длинное.
Как говорилось еще в альма-матер: switch - это искусственная конструкция, оптимизированная  для работы с целыми числами.
Как мне помнится, в Java Language Specification и Java VM specification придерживаются тех же принципов. Хотя, если есть желание - можете javap'нуть код и посмотреть на сгенеренные команды (и посчитать какой выигрыш в инструкциях будет - опять же, если есть желание и свободное время).

Автор: Royan 1.9.2009, 17:03
Цитата(revenforv @  1.9.2009,  13:44 Найти цитируемый пост)
Вообще-то тест несостоятелен по двум причинам:
- он искусственный;
- вы используете массив int, операции над которыми в jvm хорошо-оптимизированы.

Как бы вы его модифицировали, чтобы приблизить к реальным условиям? Вы же не будете спорить с тем, что операции if(){} else if(){} используются довольно часто. Цель данного теста проверить действительно ли switch может оказать быстрее чем if при прочих равных условиях.

Цитата(revenforv @  1.9.2009,  13:44 Найти цитируемый пост)
Как говорилось еще в альма-матер: switch - это искусственная конструкция, оптимизированная  для работы с целыми числами

В java switch работает в т.ч. с перечислениями можно попробовать переписать тест с enum'ами и сравнить, может быть к ночи ближе перепишу.

Автор: revenforv 1.9.2009, 17:07
 smile Да я то с вами полностью согласен, просто до сих пор помнится, как меня заставили на 3 курсе переписывать курсовую на C, в которой в for'е проверялось логическое условие, а не счетчик по массиву. Пришлось все for'ы переделывать в while. smile 

Ну это так - отступление от темы.. а вообще - я почитаю jls и скажу, что там написано на этот счет.

Автор: COVD 1.9.2009, 17:07
Цитата

Держу пари на 5 золотых, что никто не проверял это на практике 

Проиграете. Думаю, многие этим баловались. Я тоже когда-то экспериментировал. Теперь не заморачиваюсь ловлей блох и делаю как нагляднее. Есть ведь еще варианты. Например, в мап кладутся обьекты - екзекюторы. И осуществляется выбор нужного обьекта по ключу за один шаг. Разновидность этого подхода - положить экзекюторы в массив и извлекать по целочисленному индексу (в вашем эксперименте это temp). 
Выжимать 4% не очень благодарное занятие. Со временем убунту поменяется на шмубунту, выйдет новая версия java и все может быть наоборот. А для клиентских программ, бегающих в совершенно разных условиях, это еще более неопределенно. Ну и любой тюнинг производительности обычно идет в ущерб наглядности программы. Если количество пользователей системы возрастает и сервера не справляются, то масштабирование, подключение новых компьютеров, является более радикальным выходом.    

Автор: fixxer 1.9.2009, 17:42
Вообще некузяво бы еще версию java указывать

Код

java version "1.6.0_14"
Java(TM) SE Runtime Environment (build 1.6.0_14-b08)
Java HotSpot(TM) Client VM (build 14.0-b16, mixed mode, sharing)


Код

Core 2 Duo E4600 2.4GHz
Windows XP SP3


Код

DefinedIf,RandomIf,DefinedSw,RandomSw
1497,29,6815,50,1445,37,6813,20


Добавлено @ 17:43
Да еще, тест строго однопоточен - второе ядро вообще не задействовано было

Автор: LSD 1.9.2009, 18:19
Цитата(Royan @  1.9.2009,  15:19 Найти цитируемый пост)
Ну речь о разнице в 4,5% на моем примере. Положим, что в приложении данные конструкции используются в каком-нибудь высоконагруженном месте, тогда эти 4,5% весьма интересны

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




Цитата
java version "1.6.0_11"
Java™ SE Runtime Environment (build 1.6.0_11-b03)
Java HotSpot™ Client VM (build 11.0-b16, mixed mode, sharing)

DefinedIf,RandomIf,DefinedSw,RandomSw
1542,43,5806,48,1477,45,5810,01

Машина: Intel Xeon E5410 2.33 GHz, 2 GB RAM, Windows XP.

Автор: Royan 1.9.2009, 18:22
Цитата(COVD @  1.9.2009,  14:07 Найти цитируемый пост)
Проиграете. Думаю, многие этим баловались. Я тоже когда-то экспериментировал. Теперь не заморачиваюсь ловлей блох и делаю как нагляднее. Есть ведь еще варианты. Например, в мап кладутся обьекты - екзекюторы. И осуществляется выбор нужного обьекта по ключу за один шаг. Разновидность этого подхода - положить экзекюторы в массив и извлекать по целочисленному индексу (в вашем эксперименте это temp).  

Понимаю. У меня желание написать тест родилось после того как я столкнулся в коде с необходимостью проверки внутреннего типа persistent cache'а. В зависимости от этого типа производилось то или иное действие. Тут нет проблемы что-то за экзекютить, к тому же для такой задачи больше подходит решение с ThreadPoolExecutor'ом

Автор: COVD 1.9.2009, 18:54
Цитата

Тут нет проблемы что-то за экзекютить, к тому же для такой задачи больше подходит решение с ThreadPoolExecutor'ом 

Экзекютор не в смысле задания, которое ставится в очередь и выполняется в отдельном потоке. Если внутри каждого if (или case) больше одной строчки кода, то может быть удобным оформить их методами разных обьектов (например, run() в анонимных Runnable ). Компактнее и возможно нагляднее будет выглядеть код. А выполняться все будет в том же потоке. 
Возможно, правильнее называть эти обьекты хэндлерами или процессорами.

Автор: Galaran 1.9.2009, 20:29
Код

Core 2 Duo E8500 3.16 GHz x 2

Ubuntu 9.04

java version "1.6.0_14"
Java(TM) SE Runtime Environment (build 1.6.0_14-b08)
Java HotSpot(TM) Server VM (build 14.0-b16, mixed mode)


Код

DefinedIf,RandomIf,DefinedSw,RandomSw
1451,13,5815,59,1409,69,5938,24


Работало на одном ядре

Автор: fixxer 1.9.2009, 22:27
Попробовал дома:

Код

java version "1.6.0_10"
Java(TM) SE Runtime Environment (build 1.6.0_10-b33)
Java HotSpot(TM) 64-Bit Server VM (build 11.0-b15, mixed mode)

Core 2 Duo E8400 3GHz
Ubuntu 8.04 LTS x86_64


Код

DefinedIf,RandomIf,DefinedSw,RandomSw
1022,68,2920,35,938,67,2906,47


 smile 



Автор: VSergeyV 2.9.2009, 12:26
Код

java version "1.5.0_10"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_10-b03)
Java HotSpot(TM) Client VM (build 1.5.0_10-b03, mixed mode, sharing)


OS: Windows Vista Business Service Pack 1
CPU: Intel Pentium D CPU 2.80GHz
     32-bit OS  
RAM: 2 GB


Код

DefinedIf,RandomIf,DefinedSw,RandomSw
2807,59,12140,73,2526,72,11460,62


Добавлено через 12 минут и 21 секунду
Код

java version "1.6.0_11"
Java(TM) SE Runtime Environment (build 1.6.0_11-b03)
Java HotSpot(TM) Client VM (build 11.0-b16, mixed mode, sharing)


OS: Windows Vista Business Service Pack 1
CPU: Intel Pentium D CPU 2.80GHz
     32-bit OS  
RAM: 2 GB


Код

DefinedIf,RandomIf,DefinedSw,RandomSw
2281,53,9380,82,2302,30,9377,77

Автор: VSergeyV 2.9.2009, 12:43
Цитата(Hidrag @  1.9.2009,  14:44 Найти цитируемый пост)
как то раз я декомпилировал свой код (потерял исходники) так в нем увидел что при компиляции мои ифы были заменены на свитчи, и многие циклы были изменены на while(true) с условием на брик внутри 

а может эта замена как раз при декомпиляции случилась?

Автор: Maksym 2.9.2009, 13:34
Код

Microsoft Windows XP Professional Version 2002 Service Pack 3

CPU Intel Core 2 Duo CPU T9400 2.53GHz
2,99 GB of RAM

java version "1.6.0_16"
Java(TM) SE Runtime Environment (build 1.6.0_16-b01)
Java HotSpot(TM) Client VM (build 14.2-b01, mixed mode, sharing)

VM argument: -Xmx512m


Код

DefinedIf,RandomIf,DefinedSw,RandomSw
1387.33,5360.12,1354.42,5269.07

Автор: Royan 2.9.2009, 14:11
Всем поучаствовавшим большое спасибо!

Вывод в лабораторных условиях switch, быстрее if, но в реальной ситуации разницы нет и можно использовать любую конструкцию.

Автор: _Y_ 2.9.2009, 20:33
Я тоже когда-то этим заинтересовался - года три назад. Написал код подлиннее и именно со случайными данными. Заставил посчитать с учетом дисперсии. Получилось, что скорости достоверно не различаются даже для очень жестких условий вероятности. 

Но вот на разных компах я не сравнивал. 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)