Предыдущий"Если быстрее или переключаться быстрее? Расшифруй секреты переключателя"Мы проверили производительность if и switch и пришли к выводу, что switch нужно использовать как можно чаще, потому что его эффективность намного выше, чем if.По конкретным причинам, нажмите на ссылку выше, чтобы посмотреть.
Поскольку переключатель - это такая прелесть, тоЕсть ли лучший способ сделать переключение быстрее??
Ответ положительный, иначе эта статья не родилась бы, не так ли?
В последней статье о сравнении производительности if и switch читатель спросил: выше ли производительность switch типа String, чем if? Сначала ответь,Производительность переключателя условного суждения строкового типа по-прежнему лучше, чем если бы.
Сарафанное радио бесполезно, давайте сначала возьмем 🌰, тестовый код выглядит следующим образом:
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
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 = 3, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class SwitchOptimizeByStringTest {
static String _STR = "Java中文社群";
public static void main(String[] args) throws RunnerException {
// 启动基准测试
Options opt = new OptionsBuilder()
.include(SwitchOptimizeByStringTest.class.getSimpleName()) // 要导入的测试类
.build();
new Runner(opt).run(); // 执行测试
}
@Benchmark
public void switchTest(Blackhole blackhole) {
String s1;
switch (_STR) {
case "java":
s1 = "java";
break;
case "mysql":
s1 = "mysql";
break;
case "oracle":
s1 = "oracle";
break;
case "redis":
s1 = "redis";
break;
case "mq":
s1 = "mq";
break;
case "kafka":
s1 = "kafka";
break;
case "rabbitmq":
s1 = "rabbitmq";
break;
default:
s1 = "default";
break;
}
// 为了避免 JIT 忽略未被使用的结果计算,可以使用 Blackhole#consume 来保证方法被正常执行
blackhole.consume(s1);
}
@Benchmark
public void ifTest(Blackhole blackhole) {
String s1;
if ("java".equals(_STR)) {
s1 = "java";
} else if ("mysql".equals(_STR)) {
s1 = "mysql";
} else if ("oracle".equals(_STR)) {
s1 = "oracle";
} else if ("redis".equals(_STR)) {
s1 = "redis";
} else if ("mq".equals(_STR)) {
s1 = "mq";
} else if ("kafka".equals(_STR)) {
s1 = "kafka";
} else if ("rabbitmq".equals(_STR)) {
s1 = "rabbitmq";
} else {
s1 = "default";
}
// 为了避免 JIT 忽略未被使用的结果计算,可以使用 Blackhole#consume 来保证方法被正常执行
blackhole.consume(s1);
}
}
Особое примечание. В этой статье используется официальный инструмент тестирования производительности JMH (Java Microbenchmark Harness, JAVA Microbenchmark Test Suite), предоставленный Oracle для тестирования.
Результаты приведенного выше теста кода следующие:
Из столбца Score (среднее время выполнения) видно, что производительность switch по-прежнему выше, чем у if.
Примечание. Тестовая среда для этой статьи: JDK 1.8 / Mac mini (2018) / Idea 2020.1.
оптимизация производительности коммутатора
Мы знаем, что коммутатор не поддерживает String до JDK 1.7,На самом деле коммутатор поддерживает только тип int.
Тип String в JDK 1.7 на самом деле использует hashCode в качестве фактического значения переключателя при компиляции. Вышеприведенный переключатель оценивает строковый код и компилирует его в байт-код. Фактический результат выглядит следующим образом:
public static void switchTest() {
String var1 = _STR;
byte var2 = -1;
switch(var1.hashCode()) {
case -1008861826:
if (var1.equals("oracle")) {
var2 = 2;
}
break;
case -95168706:
if (var1.equals("rabbitmq")) {
var2 = 6;
}
break;
case 3492:
if (var1.equals("mq")) {
var2 = 4;
}
break;
case 3254818:
if (var1.equals("java")) {
var2 = 0;
}
break;
case 101807910:
if (var1.equals("kafka")) {
var2 = 5;
}
break;
case 104382626:
if (var1.equals("mysql")) {
var2 = 1;
}
break;
case 108389755:
if (var1.equals("redis")) {
var2 = 3;
}
}
// 忽略其他代码...
}
Зная суть реализации коммутатора, оптимизация становится относительно простой.
Как видно из приведенного выше байт-кода, если вы хотите оптимизировать переключатель, вам нужно только изменить тип String на тип int, чтобы оставить потребление производительности, если судить в каждом случае, Окончательный код оптимизации выглядит следующим образом. :
public void switchHashCodeTest() {
String s1;
switch (_STR.hashCode()) {
case 3254818:
s1 = "java";
break;
case 104382626:
s1 = "mysql";
break;
case -1008861826:
s1 = "oracle";
break;
case 108389755:
s1 = "redis";
break;
case 3492:
s1 = "mq";
break;
case 101807910:
s1 = "kafka";
break;
case -95168706:
s1 = "rabbitmq";
break;
default:
s1 = "default";
break;
}
}
На данный момент мы используем JMH для фактического теста, и тестовый код выглядит следующим образом:
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
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 = 3, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每个测试线程一个实例
public class SwitchOptimizeByStringTest {
static String _STR = "Java中文社群";
public static void main(String[] args) throws RunnerException {
// 启动基准测试
Options opt = new OptionsBuilder()
.include(SwitchOptimizeByStringTest.class.getSimpleName()) // 要导入的测试类
.build();
new Runner(opt).run(); // 执行测试
}
@Benchmark
public void switchHashCodeTest(Blackhole blackhole) {
String s1;
switch (_STR.hashCode()) {
case 3254818:
s1 = "java";
break;
case 104382626:
s1 = "mysql";
break;
case -1008861826:
s1 = "oracle";
break;
case 108389755:
s1 = "redis";
break;
case 3492:
s1 = "mq";
break;
case 101807910:
s1 = "kafka";
break;
case -95168706:
s1 = "rabbitmq";
break;
default:
s1 = "default";
break;
}
// 为了避免 JIT 忽略未被使用的结果计算,可以使用 Blackhole#consume 来保证方法被正常执行
blackhole.consume(s1);
}
@Benchmark
public void switchTest(Blackhole blackhole) {
String s1;
switch (_STR) {
case "java":
s1 = "java";
break;
case "mysql":
s1 = "mysql";
break;
case "oracle":
s1 = "oracle";
break;
case "redis":
s1 = "redis";
break;
case "mq":
s1 = "mq";
break;
case "kafka":
s1 = "kafka";
break;
case "rabbitmq":
s1 = "rabbitmq";
break;
default:
s1 = "default";
break;
}
// 为了避免 JIT 忽略未被使用的结果计算,可以使用 Blackhole#consume 来保证方法被正常执行
blackhole.consume(s1);
}
@Benchmark
public void ifTest(Blackhole blackhole) {
String s1;
if ("java".equals(_STR)) {
s1 = "java";
} else if ("mysql".equals(_STR)) {
s1 = "mysql";
} else if ("oracle".equals(_STR)) {
s1 = "oracle";
} else if ("redis".equals(_STR)) {
s1 = "redis";
} else if ("mq".equals(_STR)) {
s1 = "mq";
} else if ("kafka".equals(_STR)) {
s1 = "kafka";
} else if ("rabbitmq".equals(_STR)) {
s1 = "rabbitmq";
} else {
s1 = "default";
}
// 为了避免 JIT 忽略未被使用的结果计算,可以使用 Blackhole#consume 来保证方法被正常执行
blackhole.consume(s1);
}
}
Результаты приведенного выше теста кода следующие:
Из вышеприведенных результатов видно, чтоОценка переключения строкового типа, после оптимизации производительность улучшена в 2,4 раза., эффект замечательный.
Меры предосторожности
Приведенная выше оптимизация переключения основана на типе String, при этом нужно обратить внимание на проблему повторяющегося hashCode, например, для строк «Aa» и «BB» их hashCode равен 2112. Поэтому мы необходимо обратить внимание на такие проблемы в оптимизации.То есть, когда мы используем хэш-код, мы должны убедиться, что ценность, добавленная суждением, известна, и лучше не иметь проблемы повторного хэш-кода.Если такая проблема происходит, наше решение состоит в том, чтобы судить и присваивать значения в случае.
Другие методы оптимизации
В этой статье мы сосредоточимся на решении для оптимизации производительности коммутатора.Конечно, если учитывать производительность, мы также можем использовать более эффективные альтернативы, такие как коллекции или перечисления.Подробности см. в другой моей статье.«9 советов, как сделать ваш If Else элегантным».
Суммировать
Благодаря этой статье мы знаемSwitch по существу поддерживает только условное суждение типа int, даже тип String в JDK 1.7 будет преобразован в hashCode (int) для суждения, когда он будет окончательно скомпилирован. Но так как if equals будет использоваться в case для сравнения после компиляции в байт-код, производительность не слишком высока (лишь немного выше, чем if), поэтому мы можем напрямую конвертировать String в тип int для сравнения, тем самым избегая потребления производительности if суждение equals в случае значительно улучшает производительность коммутатора, но следует отметить, что hashCode некоторых значений ключей одинаковый, поэтому его нужно заранее избегать при оптимизации.
последние слова
Оригинальность – это непросто, если вы считаете, что эта статья вам полезна, нажмите на значок "отличный", это самая большая поддержка и поощрение для автора, спасибо.
Подпишитесь на официальный аккаунт «Java Chinese Community» и ответьте на «Галантные товары», чтобы получить 50 оригинальных галантерейных товаров.Топ-лист.