Хлоп, пощечина! Вождь сказал: попробуй-улов надо поставить вне тиража!

Java оптимизация производительности
Хлоп, пощечина! Вождь сказал: попробуй-улов надо поставить вне тиража!

Здравствуйте, дорогие друзья,Брат Лэй технологий, о прогрессе и говорить нечего! Добро пожаловать в новую серию интерпретаций перформанса, меня зовут Лэй Гэ.

То, что я приношу вам сегодня, касаетсяСтатьи о том, следует ли помещать try-catch вне или внутри цикла, мы начнемпредставлениеиАнализ бизнес-сценариевответить на этот вопрос двумя способами.

Многие люди имеют определенное непонимание try-catch, например, мы часто отождествляем его (try-catch) с «низкой производительностью», но суть try-catch (что это такое) не имеет самого элементарного понимания, поэтому мы такжеВ этой статье мы рассмотрим природу try-catch..

img

Советы: я сделаю все возможное, чтобы использовать код и результаты оценки, чтобы доказать проблему, но из-за ограничений моего собственного познания, если есть какие-либо несоответствия, пожалуйста, читатели и друзья укажите в области комментариев.

Оценка эффективности

Без лишних слов приступим к сегодняшнему тесту.В этой статье мы по-прежнему используем 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>{version}</version>
</dependency>

Полный тестовый код выглядит следующим образом:

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;

/**
 * try - catch 性能测试
 */
@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 1 轮,每次 1s
@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Benchmark)
@Threads(100)
public class TryCatchPerformanceTest {
    private static final int forSize = 1000; // 循环次数
    public static void main(String[] args) throws RunnerException {
        // 启动基准测试
        Options opt = new OptionsBuilder()
                .include(TryCatchPerformanceTest.class.getSimpleName()) // 要导入的测试类
                .build();
        new Runner(opt).run(); // 执行测试
    }

    @Benchmark
    public int innerForeach() {
        int count = 0;
        for (int i = 0; i < forSize; i++) {
            try {
                if (i == forSize) {
                    throw new Exception("new Exception");
                }
                count++;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return count;
    }

    @Benchmark
    public int outerForeach() {
        int count = 0;
        try {
            for (int i = 0; i < forSize; i++) {
                if (i == forSize) {
                    throw new Exception("new Exception");
                }
                count++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return count;
    }
}

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

img

Из приведенных выше результатов видно, что когда программа зацикливается 1000 раз, среднее время выполнения одного выполнения составляет:

  • Среднее время выполнения try-catch в цикле составляет 635 нс ±75 нс, то есть ошибка 635 нс составляет 75 нс;
  • Среднее время выполнения с try-catch вне цикла составляет 630 наносекунд с погрешностью 38 наносекунд.

То есть при отсутствии аномалий, сняв значение ошибки, получаем вывод:попробуй-поймай ли вforвнутри цикла илиforВне цикла они работают одинаково почти без разницы.

img

Суть try-catch

Чтобы разобраться в вопросах производительности try-catch, необходимо начать с анализа его байт-кода, только тогда я смогу узнать, в чем суть try-catch и как он выполняется.

На этом этапе мы пишем простейший код try-catch:

public class AppTest {
    public static void main(String[] args) {
        try {
            int count = 0;
            throw new Exception("new Exception");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

затем используйтеjavacПосле генерации байт-кода используйтеjavap -c AppTestКоманда для просмотра файла байт-кода:

➜ javap -c AppTest 
警告: 二进制文件AppTest包含com.example.AppTest
Compiled from "AppTest.java"
public class com.example.AppTest {
  public com.example.AppTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: new           #2                  // class java/lang/Exception
       5: dup
       6: ldc           #3                  // String new Exception
       8: invokespecial #4                  // Method java/lang/Exception."<init>":(Ljava/lang/String;)V
      11: athrow
      12: astore_1
      13: aload_1
      14: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
      17: return
    Exception table:
       from    to  target type
           0    12    12   Class java/lang/Exception
}

Как видно из приведенного выше байт-кода, есть таблица исключений:

Exception table:
       from    to  target type
          0    12    12   Class java/lang/Exception

Описание параметра:

  • from: указывает начальный адрес try-catch;
  • to: указывает конечный адрес try-catch;
  • цель: указывает бит начала обработки исключения;
  • тип: указывает имя класса исключений.

Из инструкций байт-кода видно, что при возникновении ошибки при выполнении кода он сначала определяет, находятся ли данные об ошибке вfromприбытьtoдиапазон, если да, то отtargetБит флага выполняется вниз, если нет ошибки, напрямуюgotoприбытьreturn. То есть, если код работает правильно, производительность почти не меняется, а логика выполнения обычного кода такая же.

img

Анализ деловой ситуации

Хотя производительность try-catch внутри и вне цикла одинакова, бизнес-значение кода, который они кодируют, совершенно различно, например, следующий код:

public class AppTest {
    public static void main(String[] args) {
        System.out.println("循环内的执行结果:" + innerForeach());
        System.out.println("循环外的执行结果:" + outerForeach());
    }
    
    // 方法一
    public static int innerForeach() {
        int count = 0;
        for (int i = 0; i < 6; i++) {
            try {
                if (i == 3) {
                    throw new Exception("new Exception");
                }
                count++;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return count;
    }

    // 方法二
    public static int outerForeach() {
        int count = 0;
        try {
            for (int i = 0; i < 6; i++) {
                if (i == 3) {
                    throw new Exception("new Exception");
                }
                count++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return count;
    }
}

Результат выполнения вышеуказанной программы:

java.lang.Exception: new Exception

at com.example.AppTest.innerForeach(AppTest.java:15)

at com.example.AppTest.main(AppTest.java:5)

java.lang.Exception: new Exception

at com.example.AppTest.outerForeach(AppTest.java:31)

at com.example.AppTest.main(AppTest.java:6)

Результат выполнения внутри цикла: 5

Результаты выполнения вне цикла: 3

Можно видеть, что try-catch в теле цикла может продолжать выполнение цикла после возникновения исключения, в то время как try-catch вне цикла завершит цикл после возникновения исключения.

поэтому мыРешение о том, следует ли помещать try-catch внутри цикла или вне цикла, не зависит от производительности (поскольку производительность почти одинакова), но должно зависеть от конкретного бизнес-сценария..

Например, нам нужно обработать пакет данных, и какие бы данные в этом наборе данных не были проблемными, это не может повлиять на нормальное выполнение других групп, в это время мы можем поместить try-catch в тело цикла ; и когда нам нужно вычислить набор данных Когда общее значение данных, пока есть ошибка в наборе данных, нам нужно прекратить выполнение и выдать исключение. В это время нам нужно поместите try-catch вне цикла для выполнения.

img

Суммировать

В этой статье мы проверили производительность try-catch, помещенного внутри цикла и вне цикла, и обнаружили, чтоПроизводительность обоих практически одинакова в случае многих циклов.. Затем с помощью анализа байт-кода мы обнаружили, что только при возникновении исключения таблица исключений будет сравниваться для обработки исключений, а выполнение try-catch при нормальных обстоятельствах может быть проигнорировано. Но использование try-catch внутри тела цикла или вне цикла совершенно различно для результата выполнения программы, поэтомуМы должны начать с фактического бизнеса, чтобы решить, где следует хранить try-catch, а не с соображений производительности..

Подпишитесь на официальный аккаунт «Java Chinese Community» и ответьте на «Галантные товары», чтобы получить 50 оригинальных галантерейных товаров.Топ-лист.