Мы знаем, что дорого обходиться исключения. Но стоит ли использовать блок try-catch в Java, даже если исключение никогда не выбрасывается?
Я нашел вопрос/вопрос о переполнении стека Почему трассировка блоков стоит дорого?, но это для .NET.
Мы знаем, что дорого обходиться исключения. Но стоит ли использовать блок try-catch в Java, даже если исключение никогда не выбрасывается?
Я нашел вопрос/вопрос о переполнении стека Почему трассировка блоков стоит дорого?, но это для .NET.
try
почти не имеет никаких расходов. Вместо выполнения задания try
во время выполнения метаданные кода структурируются во время компиляции, так что когда генерируется исключение, теперь он выполняет относительно дорогостоящую операцию по поднятию стека и наблюдение, если какой-либо try
существуют блоки, которые поймают это исключение. С точки зрения непрофессионала, try
также может быть бесплатным. Это фактически бросает исключение, которое стоит вам - но если вы не бросаете сотни или тысячи исключений, вы все равно не заметите стоимость.
try
имеет некоторые незначительные издержки, связанные с ним. Java не может делать некоторые оптимизации для кода в блоке try
, который он в противном случае сделал бы. Например, Java часто реорганизует инструкции в методе, чтобы заставить его работать быстрее, но Java также должна гарантировать, что если генерируется исключение, выполнение метода выполняется так, как если бы его инструкции, как написано в исходном коде, выполнялись до некоторой строки.
Потому что в блоке try
может быть выбрано исключение (в любой строке блока try! Некоторые исключения вызывают асинхронно, например, вызывая stop
в потоке (который устарел), и даже к тому же OutOfMemoryError может произойти почти в любом месте), и все же его можно поймать, а код продолжить выполнение в том же методе, сложнее рассуждать об оптимизации, которые могут быть сделаны, поэтому они менее вероятны. (Кто-то должен был запрограммировать компилятор, чтобы делать их, обосновывать и гарантировать правильность и т.д. Было бы большой болью для чего-то, что должно было быть "исключительным" ). Но на практике вы не заметите таких вещей.
Пусть она измеряется, будем ли мы?
public abstract class Benchmark {
final String name;
public Benchmark(String name) {
this.name = name;
}
abstract int run(int iterations) throws Throwable;
private BigDecimal time() {
try {
int nextI = 1;
int i;
long duration;
do {
i = nextI;
long start = System.nanoTime();
run(i);
duration = System.nanoTime() - start;
nextI = (i << 1) | 1;
} while (duration < 100000000 && nextI > 0);
return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
return name + "\t" + time() + " ns";
}
public static void main(String[] args) throws Exception {
Benchmark[] benchmarks = {
new Benchmark("try") {
@Override int run(int iterations) throws Throwable {
int x = 0;
for (int i = 0; i < iterations; i++) {
try {
x += i;
} catch (Exception e) {
e.printStackTrace();
}
}
return x;
}
}, new Benchmark("no try") {
@Override int run(int iterations) throws Throwable {
int x = 0;
for (int i = 0; i < iterations; i++) {
x += i;
}
return x;
}
}
};
for (Benchmark bm : benchmarks) {
System.out.println(bm);
}
}
}
На моем компьютере это печатает что-то вроде:
try 0.598 ns
no try 0.601 ns
По крайней мере, в этом тривиальном примере оператор try не оказал заметного влияния на производительность. Не стесняйтесь измерять более сложные.
Вообще говоря, я рекомендую не беспокоиться о стоимости исполнения языковых конструкций до тех пор, пока у вас не появится фактическая проблема с производительностью в вашем коде. Или как Дональд Кнут ставить его: "преждевременная оптимизация - это корень всего зла".
try
/catch
может оказать определенное влияние на производительность. Это связано с тем, что он не позволяет JVM делать некоторые оптимизации. Джошуа Блох, в "Эффективной Java", сказал следующее:
• Размещение кода внутри блока try-catch блокирует определенные оптимизации, которые могли бы выполнять современные реализации JVM.
Да, как говорили другие, блок try
блокирует некоторые оптимизации для окружающих его символов {}
. В частности, оптимизатор должен предположить, что исключение может возникать в любой точке блока, поэтому нет уверенности в том, что операторы выполняются.
Например:
try {
int x = a + b * c * d;
other stuff;
}
catch (something) {
....
}
int y = a + b * c * d;
use y somehow;
Без try
значение, рассчитанное для назначения x
, может быть сохранено как "общее подвыражение" и повторно использовано для назначения y
. Но из-за try
нет уверенности в том, что первое выражение когда-либо оценивалось, поэтому выражение должно быть пересчитано. Это обычно не имеет большого значения в "прямолинейном" коде, но может быть значительным в цикле.
Следует отметить, однако, что это относится ТОЛЬКО к JITCed-коду. javac выполняет только оптимизированную сумму, и для интерпретатора байт-кода для ввода/выхода из блока try
существует нулевая стоимость. (Нет байт-кодов, сгенерированных для отметки границ блоков.)
И для bestsss:
public class TryFinally {
public static void main(String[] argv) throws Throwable {
try {
throw new Throwable();
}
finally {
System.out.println("Finally!");
}
}
}
Вывод:
C:\JavaTools>java TryFinally
Finally!
Exception in thread "main" java.lang.Throwable
at TryFinally.main(TryFinally.java:4)
Выход javap:
C:\JavaTools>javap -c TryFinally.class
Compiled from "TryFinally.java"
public class TryFinally {
public TryFinally();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Throwable;
Code:
0: new #2 // class java/lang/Throwable
3: dup
4: invokespecial #3 // Method java/lang/Throwable."<init>":()V
7: athrow
8: astore_1
9: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
12: ldc #5 // String Finally!
14: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
17: aload_1
18: athrow
Exception table:
from to target type
0 9 8 any
}
Нет "GOTO".
Еще один микрообъект (источник).
Я создал тест, в котором я измеряю версию try-catch и no-try-catch, основанную на проценте исключения. 10% -ный процент означает, что 10% тестовых случаев имели деление на нулевые случаи. В одной ситуации он обрабатывается блоком try-catch, в другом - условным оператором. Вот таблица результатов:
OS: Windows 8 6.2 x64
JVM: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 23.25-b01
Percentage | Result (try/if, ns) 0% | 88/90 1% | 89/87 10% | 86/97 90% | 85/83
Это говорит о том, что между любыми из этих случаев нет существенной разницы.
Чтобы понять, почему оптимизация не может быть выполнена, полезно понять основные механизмы. Самый сжатый пример, который я смог найти, был реализован в макросах C: http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html
#include <stdio.h>
#include <setjmp.h>
#define TRY do{ jmp_buf ex_buf__; switch( setjmp(ex_buf__) ){ case 0: while(1){
#define CATCH(x) break; case x:
#define FINALLY break; } default:
#define ETRY } }while(0)
#define THROW(x) longjmp(ex_buf__, x)
У компиляторов часто возникают трудности с определением того, может ли скачок быть локализован в X, Y и Z, поэтому они пропускают оптимизации, которые они не могут гарантировать, чтобы быть безопасными, но сама реализация довольно легкая.
Я нашел ловушку NullPointException довольно дорого. Для операций 1.2k время составляло 200 мс и 12 мс, когда я обрабатывал его одинаково с if(object==null)
что было для меня довольно хорошим улучшением.