Затраты на перебрасывание исключения Java против использования instanceof/cast
Я знаю, что издержки Java-исключений были сделаны до смерти на SO, но я не нашел ничего, что соответствовало бы моей ситуации. У меня есть будущее, которое при вызове get() может генерировать исключение ExecutionException, содержащее любое количество исключений для конкретного приложения. Мне было интересно, есть ли значительные издержки при использовании более симпатичного блока try-catch вместо уродливого шаблона if-instanceof-then-cast. Например, это может выглядеть примерно так:
private Response handleException(ExecutionException e) throws MyApplicationException {
try {
throw e.getCause();
} catch (ApplicationException1 e1) {
// known error
throw MyApplicationException.convert(e1);
} catch (ApplicationException2 e2) {
// create error response
return new Response(e2);
} catch (Throwable t) {
// unknown error
throw new RuntimeException(t);
}
}
private Response handleException2(ExecutionException e) throws MyApplicationException {
Throwable cause = e.getCause();
if (cause instanceof ApplicationException1) {
ApplicationException1 e1 = (ApplicationException1) cause;
throw MyApplicationException.convert(e1);
} else if (cause instanceof ApplicationException2) {
ApplicationException2 e2 = (ApplicationException2) cause;
return new Response(e2);
} else {
throw new RuntimeException(cause);
}
}
Моя теория состоит в том, что не должно быть огромное количество накладных расходов, так как
- Исключение и трассировка стека уже созданы.
- В любом случае я выполняю отражение объекта исключения в обоих методах.
- Исключение поймано немедленно и никогда не распространяется.
3 ответа
Что касается стиля, я обычно рекомендую не использовать обработчики исключений для регулярного потока управления. Я могу видеть аргумент для использования здесь, хотя, как дизайн Future
требует от вас "развернуть" исходное исключение.
Восстановление исключения должно быть существенно дешевле, чем создание нового исключения, поскольку трассировка стека уже заполнена. Ваш первый подход может потребовать дополнительных затрат, но если ваше приложение выдает так много исключений, что влияние становится заметным, то у вас, вероятно, будут большие проблемы.
Если это действительно беспокоит вас, единственный способ получить значимый ответ - измерить разницу самостоятельно. Но, опять же, исключения должны быть выброшены только в исключительных случаях; они должны быть необычными по дизайну. Даже если вы удвоите стоимость обработки исключений, она составит всего 2n вместо n. Если вы выбрасываете так много исключений, что производительность вашего приложения заметно снижается, простой фактор в два, вероятно, не сделает или сломает вас. Поэтому используйте любой стиль, который вы найдете более читабельным.
Если вы хотите, чтобы второй пример выглядел лучше, вы всегда можете выполнить приведение при использовании исключения причины:
private Response handleException2(ExecutionException e) throws MyApplicationException {
Throwable cause = e.getCause();
if (cause instanceof ApplicationException1) {
throw MyApplicationException.convert((ApplicationException1) cause);
} else if (cause instanceof ApplicationException2) {
return new Response((ApplicationException2) cause);
} else {
throw new RuntimeException(cause);
}
}
ОБНОВЛЕНО ОТ ОРИГИНАЛА Было довольно сложно написать тривиальный код, который компилятор HotSpot ни к чему не привел, но я думаю, что следующее хорошо:
package net.redpoint.utils;
public class Scratch {
public long counter = 0;
public class A {
public void inc() { counter++; }
}
public class B extends A {
public void inc() { counter++; }
}
public class C extends A {
public void inc() { counter++; }
}
public A[] a = new A[3];
public void test() {
a[0] = new A();
a[1] = new B();
a[2] = new C();
int iter = 100000000;
long start = System.nanoTime();
for(int i = iter; i > 0; i--) {
testUsingInstanceOf(a[i%3]);
}
long end = System.nanoTime();
System.out.println("instanceof: " + iter / ((end - start) / 1000000000.0) + " per second");
start = System.nanoTime();
for(int i = iter; i > 0; i--) {
testUsingException(a[i%3]);
}
end = System.nanoTime();
System.out.println("try{}: " + iter / ((end - start) / 1000000000.0) + " per second");
start = System.nanoTime();
for(int i = iter; i > 0; i--) {
testUsingClassName(a[i%3]);
}
end = System.nanoTime();
System.out.println("classname: " + iter / ((end - start) / 1000000000.0) + " per second");
}
public static void main(String[] args) {
Scratch s = new Scratch();
s.test();
}
public void testUsingInstanceOf(A possiblyB){
if (possiblyB instanceof B){
((B)possiblyB).inc();
}
}
public void testUsingException(A possiblyB){
try{
((B)possiblyB).inc();
} catch(Exception e){
}
}
public void testUsingClassName(A possiblyB){
if (possiblyB.getClass().getName().equals("net.redpoint.utils.Scratch$B")){
((B)possiblyB).inc();
}
}
}
Полученный результат:
instanceof: 4.573174070960945E8 per second
try{}: 3.926650051387284E8 per second
classname: 7.689439655530204E7 per second
Тестирование проводилось с использованием Oracle JRE7 SE на Windows 8 x64, с процессором Intel i7 Sandy Bridge.