После использования этого трюка производительность коммутатора улучшилась в 3 раза!

Java оптимизация производительности
После использования этого трюка производительность коммутатора улучшилась в 3 раза!

Предыдущий"Если быстрее или переключаться быстрее? Расшифруй секреты переключателя"Мы проверили производительность 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 для тестирования.

Результаты приведенного выше теста кода следующие:

img

Из столбца 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);
    }
}

Результаты приведенного выше теста кода следующие:

img

Из вышеприведенных результатов видно, чтоОценка переключения строкового типа, после оптимизации производительность улучшена в 2,4 раза., эффект замечательный.

img

Меры предосторожности

Приведенная выше оптимизация переключения основана на типе 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 оригинальных галантерейных товаров.Топ-лист.