если быстрее или свитч быстрее? Расшифровка секретов переключателя

Java оптимизация производительности
если быстрее или свитч быстрее? Расшифровка секретов переключателя

Оператор условного суждения является важной частью программы, а также методом управления бизнес-логикой системы. Важность и частота использования не имеют себе равных, так как же мы выбираем, если или переключиться? Насколько велика разница в их производительности? В чем секрет производительности коммутатора? Давайте вместе найдем ответы на эти вопросы.

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;
        }
    }
}

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

img

Примечание. Тестовая среда для этой статьи: 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;
        }
    }
}

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

img

Из значения 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;
        }
    }
}

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

img

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