Оператор условного суждения является важной частью программы, а также методом управления бизнес-логикой системы. Важность и частота использования не имеют себе равных, так как же мы выбираем, если или переключиться? Насколько велика разница в их производительности? В чем секрет производительности коммутатора? Давайте вместе найдем ответы на эти вопросы.
switch VS if
моя предыдущая статья«9 советов, как сделать ваш If Else элегантным»Было упомянуто, что коммутатор следует использовать как можно чаще, поскольку его производительность относительно высока, но насколько она высока? А причина, по которой она высока, будет раскрыта вам в этой статье.
Мы по-прежнему используем инфраструктуру JMH (Java Microbenchmark Harness, JAVA Microbenchmark Test Suite), официально предоставленную Oracle для тестирования.Сначала мы вводим инфраструктуру JMH и добавляем следующую конфигурацию в файл pom.xml:
<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.23</version>
</dependency>
Затем напишите тестовый код. Здесь мы добавляем 5 ветвей условного суждения. Конкретный код реализации выглядит следующим образом:
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 1s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class SwitchOptimizeTest {
static Integer _NUM = 9;
public static void main(String[] args) throws RunnerException {
// 启动基准测试
Options opt = new OptionsBuilder()
.include(SwitchOptimizeTest.class.getSimpleName()) // 要导入的测试类
.output("/Users/admin/Desktop/jmh-switch.log") // 输出测试结果的文件
.build();
new Runner(opt).run(); // 执行测试
}
@Benchmark
public void switchTest() {
int num1;
switch (_NUM) {
case 1:
num1 = 1;
break;
case 3:
num1 = 3;
break;
case 5:
num1 = 5;
break;
case 7:
num1 = 7;
break;
case 9:
num1 = 9;
break;
default:
num1 = -1;
break;
}
}
@Benchmark
public void ifTest() {
int num1;
if (_NUM == 1) {
num1 = 1;
} else if (_NUM == 3) {
num1 = 3;
} else if (_NUM == 5) {
num1 = 5;
} else if (_NUM == 7) {
num1 = 7;
} else if (_NUM == 9) {
num1 = 9;
} else {
num1 = -1;
}
}
}
Результаты тестирования приведенного выше кода следующие:
Примечание. Тестовая среда для этой статьи: JDK 1.8 / Mac mini (2018) / Idea 2020.1.
Как видно из приведенных выше результатов (столбец Score),Среднее время выполнения команды switch примерно в 2,33 раза быстрее, чем среднее время выполнения команды if..
анализ производительности
Почему производительность коммутатора намного выше, чем производительность if?
Это должно начинаться с их байт-кода, мы используем их код для использованияjavac
Сгенерированный байт-код выглядит так:
public class com.example.optimize.SwitchOptimize {
static java.lang.Integer _NUM;
public com.example.optimize.SwitchOptimize();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #7 // Method switchTest:()V
3: invokestatic #12 // Method ifTest:()V
6: return
public static void switchTest();
Code:
0: getstatic #15 // Field _NUM:Ljava/lang/Integer;
3: invokevirtual #19 // Method java/lang/Integer.intValue:()I
6: tableswitch { // 1 to 9
1: 56
2: 83
3: 61
4: 83
5: 66
6: 83
7: 71
8: 83
9: 77
default: 83
}
56: iconst_1
57: istore_0
58: goto 85
61: iconst_3
62: istore_0
63: goto 85
66: iconst_5
67: istore_0
68: goto 85
71: bipush 7
73: istore_0
74: goto 85
77: bipush 9
79: istore_0
80: goto 85
83: iconst_m1
84: istore_0
85: return
public static void ifTest();
Code:
0: getstatic #15 // Field _NUM:Ljava/lang/Integer;
3: invokevirtual #19 // Method java/lang/Integer.intValue:()I
6: iconst_1
7: if_icmpne 15
10: iconst_1
11: istore_0
12: goto 81
15: getstatic #15 // Field _NUM:Ljava/lang/Integer;
18: invokevirtual #19 // Method java/lang/Integer.intValue:()I
21: iconst_3
22: if_icmpne 30
25: iconst_3
26: istore_0
27: goto 81
30: getstatic #15 // Field _NUM:Ljava/lang/Integer;
33: invokevirtual #19 // Method java/lang/Integer.intValue:()I
36: iconst_5
37: if_icmpne 45
40: iconst_5
41: istore_0
42: goto 81
45: getstatic #15 // Field _NUM:Ljava/lang/Integer;
48: invokevirtual #19 // Method java/lang/Integer.intValue:()I
51: bipush 7
53: if_icmpne 62
56: bipush 7
58: istore_0
59: goto 81
62: getstatic #15 // Field _NUM:Ljava/lang/Integer;
65: invokevirtual #19 // Method java/lang/Integer.intValue:()I
68: bipush 9
70: if_icmpne 79
73: bipush 9
75: istore_0
76: goto 81
79: iconst_m1
80: istore_0
81: return
static {};
Code:
0: iconst_1
1: invokestatic #25 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: putstatic #15 // Field _NUM:Ljava/lang/Integer;
7: return
}
Наиболее важной информацией в этих байт-кодах является «getstatic #15», что означает удаление переменной «_NUM» и условий для оценки.
Как видно из приведенного выше байт-кода,В switch переменная и условие берутся для сравнения только один раз, тогда как в if переменная и условие берутся для сравнения каждый раз, поэтому эффективность if будет намного медленнее, чем у switch..
Увеличить тестовый объем
В предыдущем тестовом коде мы использовали 5 условий ветвления для проверки производительности if и switch.Что произойдет с результатами теста, если увеличить условия оценки ветвления в 3 раза (15)?
Код реализации, добавленный к 15 отраслевым суждениям, выглядит следующим образом:
package com.example.optimize;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class SwitchOptimizeTest {
static Integer _NUM = 1;
public static void main(String[] args) throws RunnerException {
// 启动基准测试
Options opt = new OptionsBuilder()
.include(SwitchOptimizeTest.class.getSimpleName()) // 要导入的测试类
.output("/Users/admin/Desktop/jmh-switch.log") // 输出测试结果的文件
.build();
new Runner(opt).run(); // 执行测试
}
@Benchmark
public void switchTest() {
int num1;
switch (_NUM) {
case 1:
num1 = 1;
break;
case 2:
num1 = 2;
break;
case 3:
num1 = 3;
break;
case 4:
num1 = 4;
break;
case 5:
num1 = 5;
break;
case 6:
num1 = 6;
break;
case 7:
num1 = 7;
break;
case 8:
num1 = 8;
break;
case 9:
num1 = 9;
break;
case 10:
num1 = 10;
break;
case 11:
num1 = 11;
break;
case 12:
num1 = 12;
break;
case 13:
num1 = 13;
break;
case 14:
num1 = 14;
break;
case 15:
num1 = 15;
break;
default:
num1 = -1;
break;
}
}
@Benchmark
public void ifTest() {
int num1;
if (_NUM == 1) {
num1 = 1;
} else if (_NUM == 2) {
num1 = 2;
} else if (_NUM == 3) {
num1 = 3;
} else if (_NUM == 4) {
num1 = 4;
} else if (_NUM == 5) {
num1 = 5;
} else if (_NUM == 6) {
num1 = 6;
} else if (_NUM == 7) {
num1 = 7;
} else if (_NUM == 8) {
num1 = 8;
} else if (_NUM == 9) {
num1 = 9;
} else if (_NUM == 10) {
num1 = 10;
} else if (_NUM == 11) {
num1 = 11;
} else if (_NUM == 12) {
num1 = 12;
} else if (_NUM == 13) {
num1 = 13;
} else if (_NUM == 14) {
num1 = 14;
} else if (_NUM == 15) {
num1 = 15;
} else {
num1 = -1;
}
}
}
Результаты тестирования приведенного выше кода следующие:
Из значения Score видно, что при увеличении количества оценок ветвления до 15 производительность коммутатора примерно в 3,7 раза выше, чем у if, а предыдущий результат теста, когда имеется 5 оценок ветвления, заключается в том, что производительность переключателя лучше, чем у ес., примерно в 2,3 раза выше, а это значит, чтоЧем больше условий оценки ветви, тем очевиднее признак высокой производительности коммутатора..
переключить секрет
Для переключателя байт-код, который он в конечном итоге генерирует, имеет две формы: один — tableswitch, а другой — lookupswitch. Окончательная форма сгенерированного кода зависит от того, является ли переключатель компактным или нет. Например, когда 1... 2...3...4 - это последовательное возрастающее условие оценки, используется tableswitch, а некомпактное подобное case 1...33...55...22 При оценке условия используется lookupswitch , Тестовый код выглядит следующим образом:
public class SwitchOptimize {
static Integer _NUM = 1;
public static void main(String[] args) {
tableSwitchTest();
lookupSwitchTest();
}
public static void tableSwitchTest() {
int num1;
switch (_NUM) {
case 1:
num1 = 1;
break;
case 2:
num1 = 2;
break;
case 3:
num1 = 3;
break;
case 4:
num1 = 4;
break;
case 5:
num1 = 5;
break;
case 6:
num1 = 6;
break;
case 7:
num1 = 7;
break;
case 8:
num1 = 8;
break;
case 9:
num1 = 9;
break;
default:
num1 = -1;
break;
}
}
public static void lookupSwitchTest() {
int num1;
switch (_NUM) {
case 1:
num1 = 1;
break;
case 11:
num1 = 2;
break;
case 3:
num1 = 3;
break;
case 4:
num1 = 4;
break;
case 19:
num1 = 5;
break;
case 6:
num1 = 6;
break;
case 33:
num1 = 7;
break;
case 8:
num1 = 8;
break;
case 999:
num1 = 9;
break;
default:
num1 = -1;
break;
}
}
}
Соответствующий байт-код выглядит следующим образом:
public class com.example.optimize.SwitchOptimize {
static java.lang.Integer _NUM;
public com.example.optimize.SwitchOptimize();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #7 // Method tableSwitchTest:()V
3: invokestatic #12 // Method lookupSwitchTest:()V
6: return
public static void tableSwitchTest();
Code:
0: getstatic #15 // Field _NUM:Ljava/lang/Integer;
3: invokevirtual #19 // Method java/lang/Integer.intValue:()I
6: tableswitch { // 1 to 9
1: 56
2: 61
3: 66
4: 71
5: 76
6: 81
7: 87
8: 93
9: 99
default: 105
}
56: iconst_1
57: istore_0
58: goto 107
61: iconst_2
62: istore_0
63: goto 107
66: iconst_3
67: istore_0
68: goto 107
71: iconst_4
72: istore_0
73: goto 107
76: iconst_5
77: istore_0
78: goto 107
81: bipush 6
83: istore_0
84: goto 107
87: bipush 7
89: istore_0
90: goto 107
93: bipush 8
95: istore_0
96: goto 107
99: bipush 9
101: istore_0
102: goto 107
105: iconst_m1
106: istore_0
107: return
public static void lookupSwitchTest();
Code:
0: getstatic #15 // Field _NUM:Ljava/lang/Integer;
3: invokevirtual #19 // Method java/lang/Integer.intValue:()I
6: lookupswitch { // 9
1: 88
3: 98
4: 103
6: 113
8: 125
11: 93
19: 108
33: 119
999: 131
default: 137
}
88: iconst_1
89: istore_0
90: goto 139
93: iconst_2
94: istore_0
95: goto 139
98: iconst_3
99: istore_0
100: goto 139
103: iconst_4
104: istore_0
105: goto 139
108: iconst_5
109: istore_0
110: goto 139
113: bipush 6
115: istore_0
116: goto 139
119: bipush 7
121: istore_0
122: goto 139
125: bipush 8
127: istore_0
128: goto 139
131: bipush 9
133: istore_0
134: goto 139
137: iconst_m1
138: istore_0
139: return
static {};
Code:
0: iconst_1
1: invokestatic #25 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: putstatic #15 // Field _NUM:Ljava/lang/Integer;
7: return
}
Из приведенного выше байт-кода видно, что tableSwitchTest использует tableswitch, а lookupSwitchTest использует lookupswitch.
tableswitch VS lookupSwitchTest
Когда выполняется tableswitch, значение int в верхней части стека используется непосредственно в качестве индекса в таблице для захвата цели перехода и немедленного выполнения перехода. То есть структура хранения tableswitch аналогична массиву, а элементы получаются напрямую по индексу, поэтому временная сложность всего запроса составляет O(1), что также означает, что его скорость поиска очень высока.
При выполнении lookupswitch ветви сравниваются одна за другой или запрос выполняется с использованием метода дихотомии, поэтому временная сложность запроса составляет O(log n),Таким образом, использование lookupswitch будет медленнее, чем tableswitch..
Затем мы используем фактический код для проверки производительности между ними.Тестовый код выглядит следующим образом:
package com.example.optimize;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class SwitchOptimizeTest {
static Integer _NUM = -1;
public static void main(String[] args) throws RunnerException {
// 启动基准测试
Options opt = new OptionsBuilder()
.include(SwitchOptimizeTest.class.getSimpleName()) // 要导入的测试类
.build();
new Runner(opt).run(); // 执行测试
}
@Benchmark
public void tableSwitchTest() {
int num1;
switch (_NUM) {
case 1:
num1 = 1;
break;
case 2:
num1 = 2;
break;
case 3:
num1 = 3;
break;
case 4:
num1 = 4;
break;
case 5:
num1 = 5;
break;
case 6:
num1 = 6;
break;
case 7:
num1 = 7;
break;
case 8:
num1 = 8;
break;
case 9:
num1 = 9;
break;
default:
num1 = -1;
break;
}
}
@Benchmark
public void lookupSwitchTest() {
int num1;
switch (_NUM) {
case 1:
num1 = 1;
break;
case 11:
num1 = 2;
break;
case 3:
num1 = 3;
break;
case 4:
num1 = 4;
break;
case 19:
num1 = 5;
break;
case 6:
num1 = 6;
break;
case 33:
num1 = 7;
break;
case 8:
num1 = 8;
break;
case 999:
num1 = 9;
break;
default:
num1 = -1;
break;
}
}
}
Результаты тестирования приведенного выше кода следующие:
Видно, что когда ветвь оценивается как 9,Производительность tableswitch примерно в 1,3 раза выше, чем у lookupswitch. Но даже в этом случае lookupswitch по-прежнему намного более эффективен, чем если бы запрос.
Суммировать
Когда условия оценки переключателя равны 5, производительность примерно в 2,3 раза выше, чем у if.А чем больше условий суждения, тем больше разница в их выполнении.. Когда переключатель скомпилирован в байт-код, он будет генерировать два типа кодов в зависимости от того, является ли условие суждения о переключателе компактным: tableswitch (создается при компактности) и lookupswitch (создается при некомпактности), среди которых tableswitch принимает хранилище, подобное массиву. структура, непосредственно элементы запроса в соответствии с индексом; в то время как lookupswitch должен запрашивать один за другим или использовать дихотомический запрос,Таким образом, tableswitch будет работать лучше, чем lookupswitch, но переключатель будет работать лучше, чем если бы в любом случае.
последние слова
Оригинальность – это непросто, если вы считаете, что эта статья вам полезна, нажмите на значок "отличный", это самая большая поддержка и поощрение для автора, спасибо.
Ссылки и благодарности
Woohoo. Java guides.net/2020/03/5 - нет...
Подпишитесь на официальный аккаунт «Java Chinese Community» и ответьте на «Галантные товары», чтобы получить 50 оригинальных галантерейных товаров.Топ-лист.