Насколько медленны исключения Java?

Вопрос: Обработка исключений в Java на самом деле медленная?

Обычная мудрость, а также множество результатов Google говорят, что исключительная логика не должна использоваться для нормального streamа программ в Java. Обычно приводятся две причины:

  1. он действительно медленный – даже на порядок медленнее обычного кода (приведенные причины варьируются),

а также

  1. это грязно, потому что люди ожидают, что только ошибки будут обработаны в исключительном коде.

Этот вопрос касается №1.

В качестве примера эта страница описывает обработку исключений Java как «очень медленную» и связывает медленность с созданием строки сообщения об исключении – «эта строка затем используется при создании объекта исключения, который вызывается. Это не быстро». В статье « Эффективная обработка исключений в Java» говорится, что «причина этого связана с аспектом создания объекта обработки исключений, что делает тем самым исключение метаданных исключениями». Другая причина в том, что генерация трассировки стека – это то, что замедляет ее.

Мое тестирование (с использованием Java 1.6.0_07, Java HotSpot 10.0, на 32-разрядном Linux) указывает, что обработка исключений не медленнее обычного кода. Я попытался запустить метод в цикле, который выполняет некоторый код. В конце метода я использую логическое значение, чтобы указать, следует ли возвращать или бросать . Таким образом, фактическая обработка одинакова. Я пытался использовать методы в разных порядках и усреднять время тестирования, думая, что это, возможно, прогревало JVM. Во всех моих тестах бросок был как минимум быстрым, чем возврат, если не быстрее (до 3,1% быстрее). Я полностью открыт для возможности того, что мои тесты были неправильными, но я не видел ничего на пути к образцу кода, сравнению тестов или результатов за последний год или два, которые показывают обработку исключений в Java, на самом деле медленный.

Что привело меня по этому пути, я использовал API, который мне нужен, чтобы использовать исключения, которые являются частью обычной логики управления. Я хотел исправить их в их использовании, но теперь я, возможно, не смогу. Будут ли я вместо этого хвалить их на их будущее мышление?

В статье « Эффективная обработка исключений Java» в компиляции «точно вовремя» авторы полагают, что наличие только обработчиков исключений, даже если исключений не было, достаточно, чтобы компилятор JIT не смог правильно оптимизировать код, тем самым замедляя его , Я еще не тестировал эту теорию.

Это зависит от того, как выполняются исключения. Самый простой способ – использовать setjmp и longjmp. Это означает, что все регистры процессора записываются в стек (который уже занимает некоторое время), и, возможно, необходимо создать некоторые другие данные … все это уже происходит в инструкции try. Оператор throw должен разворачивать стек и восстанавливать значения всех регистров (и возможных других значений в виртуальной машине). Таким образом, попытка и бросок одинаково медленны, и это довольно медленно, однако, если исключение не выбрасывается, выход из блока try в большинстве случаев не занимает времени (так как все помещается в стек, который автоматически очищается, если метод существует).

Солнце и другие признали, что это возможно субоптимально, и, разумеется, с течением времени виртуальные машины становятся все быстрее и быстрее. Существует еще один способ реализации исключений, который заставляет попробовать себя молниеносно (на самом деле ничего не происходит для попытки вообще вообще – все, что должно произойти, уже выполняется, когда class загружается виртуальной машиной), и это делает не совсем медленным , Я не знаю, какая JVM использует эту новую, лучшую технику …

… но вы пишете на Java, поэтому ваш код позже работает только на одной JVM на одной конкретной системе? Поскольку, если он может когда-либо работать на любой другой платформе или любой другой версии JVM (возможно, любого другого поставщика), кто говорит, что они также используют быструю реализацию? Быстрая, более сложная, чем медленная, и не всегда возможна во всех системах. Вы хотите оставаться портативным? Тогда не полагайтесь на быстрые исключения.

Это также сильно влияет на то, что вы делаете в блоке try. Если вы откроете блок try и никогда не вызываете какой-либо метод из этого блока try, блок try будет очень быстрым, так как JIT может тогда обработать бросок, как простой goto. Не нужно сохранять состояние стека и не нужно разматывать стек, если выбрано исключение (ему нужно только перейти к обработчикам catch). Однако это не то, что вы обычно делаете. Обычно вы открываете блок try, а затем вызываете метод, который может генерировать исключение, не так ли? И даже если вы просто используете блок try в своем методе, каким будет метод, который не вызывает никакого другого метода? Будет ли он просто рассчитать число? Тогда зачем вам нужны исключения? Есть гораздо более элегантные способы регулирования streamа программы. Для почти ничего, кроме простой математики, вам придется вызывать внешний метод, и это уже разрушает преимущество локального блока try.

См. Следующий тестовый код:

 public class Test { int value; public int getValue() { return value; } public void reset() { value = 0; } // Calculates without exception public void method1(int i) { value = ((value + i) / i) << 1; // Will never be true if ((i & 0xFFFFFFF) == 1000000000) { System.out.println("You'll never see this!"); } } // Could in theory throw one, but never will public void method2(int i) throws Exception { value = ((value + i) / i) << 1; // Will never be true if ((i & 0xFFFFFFF) == 1000000000) { throw new Exception(); } } // This one will regularly throw one public void method3(int i) throws Exception { value = ((value + i) / i) << 1; // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both // an AND operation between two integers. The size of the number plays // no role. AND on 32 BIT always ANDs all 32 bits if ((i & 0x1) == 1) { throw new Exception(); } } public static void main(String[] args) { int i; long l; Test t = new Test(); l = System.currentTimeMillis(); t.reset(); for (i = 1; i < 100000000; i++) { t.method1(i); } l = System.currentTimeMillis() - l; System.out.println( "method1 took " + l + " ms, result was " + t.getValue() ); l = System.currentTimeMillis(); t.reset(); for (i = 1; i < 100000000; i++) { try { t.method2(i); } catch (Exception e) { System.out.println("You'll never see this!"); } } l = System.currentTimeMillis() - l; System.out.println( "method2 took " + l + " ms, result was " + t.getValue() ); l = System.currentTimeMillis(); t.reset(); for (i = 1; i < 100000000; i++) { try { t.method3(i); } catch (Exception e) { // Do nothing here, as we will get here } } l = System.currentTimeMillis() - l; System.out.println( "method3 took " + l + " ms, result was " + t.getValue() ); } } 

Результат:

 method1 took 972 ms, result was 2 method2 took 1003 ms, result was 2 method3 took 66716 ms, result was 2 

Замедление от блока try слишком мало, чтобы исключить такие факторы, как фоновые процессы. Но блокирующий блок убил все и сделал это в 66 раз медленнее!

Как я уже сказал, результат не будет таким уж плохим, если вы поместите try / catch и выбросите все в рамках одного и того же метода (method3), но это специальная оптимизация JIT, на которую я бы не рассчитывал. И даже при использовании этой оптимизации бросок все еще довольно медленный. Поэтому я не знаю, что вы пытаетесь сделать здесь, но определенно лучший способ сделать это, чем использовать try / catch / throw.

FYI, я продлил эксперимент, который сделал Мечки:

 method1 took 1733 ms, result was 2 method2 took 1248 ms, result was 2 method3 took 83997 ms, result was 2 method4 took 1692 ms, result was 2 method5 took 60946 ms, result was 2 method6 took 25746 ms, result was 2 

Первые 3 такие же, как у Mecki (мой ноутбук явно медленнее).

method4 идентичен методу3, за исключением того, что он создает new Integer(1) а не делает throw new Exception() .

method5 подобен методу 3, за исключением того, что он создает new Exception() без его выброса.

method6 подобен методу3, за исключением того, что он генерирует предварительно созданное исключение (переменную экземпляра) вместо создания нового.

В Java большая часть затрат на выброс исключения – это время, потраченное на сбор трассировки стека, которая возникает, когда создается объект исключения. Фактическая стоимость выброса исключения, в то время как большая, значительно меньше стоимости создания исключения.

Алексей Шипилев провел очень тщательный анализ, в котором он оценивает исключения Java при различных сочетаниях условий:

  • Недавно созданные исключения против предварительно созданных исключений
  • Трассировка стека против отключенной
  • Запрошенная трассировка стека vs никогда не запрашивалась
  • Пойманный на высшем уровне против восстания на каждом уровне против прикованного / завернутого на каждом уровне
  • Различные уровни глубины стека Java-вызовов
  • Отсутствие оптимизаций наложения и экстремальных вложений против настроек по умолчанию
  • Поля, определяемые пользователем, прочитаны, а не прочитаны

Он также сравнивает их с эффективностью проверки кода ошибки на разных уровнях частоты ошибок.

Выводы (цитируемые дословно из его поста) были следующими:

  1. Поистине исключительные исключения прекрасно исполняются. Если вы используете их как разработанные и обмениваетесь исключительно исключительными случаями среди подавляющего большинства не исключительных случаев, обрабатываемых обычным кодом, то использование исключений – это победа в производительности.

  2. Затраты на выполнение исключений include два основных компонента: построение трассировки стека при создании экземпляра Exception и разворачивание стека во время броска исключения.

  3. Стоимость строительства трассировки стека пропорциональна глубине стека в момент создания экземпляра исключения. Это уже плохо, потому что кто на Земле знает глубину стека, на которой будет называться этот метод метания? Даже если вы отключите генерацию трассировки стека и / или кешируете исключения, вы можете избавиться только от этой части стоимости исполнения.

  4. Расходы на разворачивание стека зависят от того, как нам повезло с тем, что обработчик исключений ближе к компилированному коду. Тщательное структурирование кода, чтобы избежать поиска обработчиков глубоких исключений, вероятно, помогает нам стать счастливее.

  5. Если мы устраним оба эффекта, стоимость выполнения исключений – это стоимость локального филиала. Независимо от того, насколько красиво это звучит, это не означает, что вы должны использовать Exceptions как обычный stream управления, потому что в этом случае вы во власти оптимизации компилятора! Вы должны использовать их только в действительно исключительных случаях, когда частота исключений амортизирует возможную неудачную стоимость повышения фактического исключения.

  6. Оптимистическое правило большого пальца, по-видимому, составляет 10 ^ -4 частоты для исключений, является достаточно исключительным. Это, конечно, зависит от тяжелых весов самих исключений, точных действий, предпринятых в обработчиках исключений и т. Д.

Результатом является то, что когда исключение не выбрасывается, вы не платите стоимость, поэтому, когда исключительное условие является достаточно редким, обработка исключений выполняется быстрее, чем при использовании if каждый раз. Полный пост очень стоит прочитать.

Мой ответ, к сожалению, слишком длинный, чтобы публиковать здесь. Поэтому давайте подытожим здесь и перейдем к http://www.fuwjax.com/how-slow-are-java-exceptions/ для подробных подробностей.

Реальный вопрос здесь заключается не в том, «насколько медленными являются« неудачи », отмеченные как исключения, по сравнению с« кодом, который никогда не сработает »?» поскольку принятый ответ мог бы вам поверить. Вместо этого возникает вопрос: «Насколько медленными являются« неудачи, сообщаемые как исключения », по сравнению с отказами, сообщенными другими способами?» Как правило, два других способа сообщения об ошибках – либо с дозорными значениями, либо с помощью оберток результатов.

Значения Sentinel – это попытка вернуть один class в случае успеха, а другой – в случае сбоя. Вы можете думать об этом почти как о возвращении исключения вместо того, чтобы его бросать. Для этого требуется общий родительский class с объектом успеха, а затем выполнить проверку «instanceof» и пару бросков, чтобы получить информацию об успехе или ошибке.

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

С другой стороны, Wrappers не жертвуют безопасностью типа. Они обертывают информацию об успехах и сбоях в одном classе. Поэтому вместо «instanceof» они предоставляют «isSuccess ()» и getters как для объектов успеха, так и для отказа. Однако объекты результатов примерно в 2 раза медленнее, чем использование исключений. Оказывается, что создание нового объекта-оболочки каждый раз намного дороже, чем иногда возникает исключение.

Кроме того, исключения – это язык, на котором указывается способ отказа метода. Нет другого способа рассказать только из API, какие методы ожидаются всегда (в основном), и которые, как ожидается, сообщают о сбое.

Исключения более безопасны, чем часовые, быстрее, чем объекты результата, и менее удивительны, чем другие. Я не предлагаю, чтобы try / catch заменили if / else, но исключения – это правильный способ сообщить об ошибке, даже в бизнес-логике.

Тем не менее, я хотел бы отметить, что два наиболее частых способа существенного влияния на производительность, с которыми я столкнулся, создают ненужные объекты и вложенные циклы. Если у вас есть выбор между созданием исключения или не созданием исключения, не создавайте исключение. Если у вас есть выбор между созданием исключения или созданием другого объекта все время, создайте исключение.

Я расширяю ответы, заданные @Mecki и @incarnate , без заполнения stacktrace для Java.

С Java 7+ мы можем использовать Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace) . Но для Java6 см. Мой ответ на этот вопрос

 // This one will regularly throw one public void method4(int i) throws NoStackTraceThrowable { value = ((value + i) / i) << 1; // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both // an AND operation between two integers. The size of the number plays // no role. AND on 32 BIT always ANDs all 32 bits if ((i & 0x1) == 1) { throw new NoStackTraceThrowable(); } } // This one will regularly throw one public void method5(int i) throws NoStackTraceRuntimeException { value = ((value + i) / i) << 1; // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both // an AND operation between two integers. The size of the number plays // no role. AND on 32 BIT always ANDs all 32 bits if ((i & 0x1) == 1) { throw new NoStackTraceRuntimeException(); } } public static void main(String[] args) { int i; long l; Test t = new Test(); l = System.currentTimeMillis(); t.reset(); for (i = 1; i < 100000000; i++) { try { t.method4(i); } catch (NoStackTraceThrowable e) { // Do nothing here, as we will get here } } l = System.currentTimeMillis() - l; System.out.println( "method4 took " + l + " ms, result was " + t.getValue() ); l = System.currentTimeMillis(); t.reset(); for (i = 1; i < 100000000; i++) { try { t.method5(i); } catch (RuntimeException e) { // Do nothing here, as we will get here } } l = System.currentTimeMillis() - l; System.out.println( "method5 took " + l + " ms, result was " + t.getValue() ); } 

Выход с Java 1.6.0_45, на Core i7, 8 ГБ ОЗУ:

 method1 took 883 ms, result was 2 method2 took 882 ms, result was 2 method3 took 32270 ms, result was 2 // throws Exception method4 took 8114 ms, result was 2 // throws NoStackTraceThrowable method5 took 8086 ms, result was 2 // throws NoStackTraceRuntimeException 

Таким образом, все еще методы, возвращающие значения, быстрее, чем методы, исключающие исключения. IMHO, мы не можем разработать прозрачный API, используя только типы возвращаемых данных для streamов успеха и ошибок. Методы, которые вызывают исключения без stacktrace, в 4-5 раз быстрее, чем обычные Исключения.

Редактировать: NoStackTraceThrowable.java Спасибо @Greg

 public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } } 

Не знаю, связаны ли эти темы, но однажды я хотел реализовать один трюк, полагающийся на трассировку стека текущего streamа: я хотел узнать имя метода, который вызвал экземпляр внутри экземпляра classа (yeap, идея сумасшедшая, Я полностью сдался). Поэтому я обнаружил, что вызов Thread.currentThread().getStackTrace() чрезвычайно медленный (из-за встроенного метода dumpThreads который он использует внутри).

Таким образом, Java Throwable , соответственно, имеет собственный метод fillInStackTrace . Я думаю, что блок убийц, описанный ранее, каким-то образом запускает выполнение этого метода.

Но позвольте мне рассказать вам еще одну историю …

В Scala некоторые функциональные возможности скомпилированы в JVM с помощью ControlThrowable , который расширяет Throwable и переопределяет его fillInStackTrace следующим образом:

 override def fillInStackTrace(): Throwable = this 

Поэтому я адаптировал тест выше (количество циклов уменьшилось на десять, моя машина немного медленнее :):

 class ControlException extends ControlThrowable class T { var value = 0 def reset = { value = 0 } def method1(i: Int) = { value = ((value + i) / i) << 1 if ((i & 0xfffffff) == 1000000000) { println("You'll never see this!") } } def method2(i: Int) = { value = ((value + i) / i) << 1 if ((i & 0xfffffff) == 1000000000) { throw new Exception() } } def method3(i: Int) = { value = ((value + i) / i) << 1 if ((i & 0x1) == 1) { throw new Exception() } } def method4(i: Int) = { value = ((value + i) / i) << 1 if ((i & 0x1) == 1) { throw new ControlException() } } } class Main { var l = System.currentTimeMillis val t = new T for (i <- 1 to 10000000) t.method1(i) l = System.currentTimeMillis - l println("method1 took " + l + " ms, result was " + t.value) t.reset l = System.currentTimeMillis for (i <- 1 to 10000000) try { t.method2(i) } catch { case _ => println("You'll never see this") } l = System.currentTimeMillis - l println("method2 took " + l + " ms, result was " + t.value) t.reset l = System.currentTimeMillis for (i <- 1 to 10000000) try { t.method4(i) } catch { case _ => // do nothing } l = System.currentTimeMillis - l println("method4 took " + l + " ms, result was " + t.value) t.reset l = System.currentTimeMillis for (i <- 1 to 10000000) try { t.method3(i) } catch { case _ => // do nothing } l = System.currentTimeMillis - l println("method3 took " + l + " ms, result was " + t.value) } 

Итак, результаты:

 method1 took 146 ms, result was 2 method2 took 159 ms, result was 2 method4 took 1551 ms, result was 2 method3 took 42492 ms, result was 2 

Понимаете, единственная разница между method3 и method4 заключается в том, что они method4 разные виды исключений. Yeap, method4 все еще медленнее method2 и method2 , но разница намного более приемлема.

Я думаю, что первая статья относится к акту прохождения стека вызовов и созданию трассировки стека как дорогостоящей части, а вторая статья не говорит об этом, я думаю, что это самая дорогая часть создания объекта. У Джона Роуза есть статья, где он описывает разные методы ускорения исключений . (Предопределение и повторное использование исключения, исключений без следов стека и т. Д.)

Но все же – я думаю, это следует рассматривать только как необходимое зло, последнее средство. Причина Джона для этого заключается в том, чтобы эмулировать функции на других языках, которые пока недоступны в JVM. Вы не должны привыкать к использованию исключений для streamа управления. Особенно не по соображениям производительности! Как вы сами упоминаете в № 2, вы рискуете маскировать серьезные ошибки в своем коде таким образом, и их будет сложнее поддерживать для новых программистов.

На Microbenchmarks на Java удивительно сложно получить право (я сказал), особенно когда вы попадаете на территорию JIT, поэтому я действительно сомневаюсь, что использование исключений происходит быстрее, чем «возrotation» в реальной жизни. Например, я подозреваю, что в вашем тесте у вас есть от 2 до 5 кадров стека? Теперь представьте, что ваш код будет вызываться компонентом JSF, развернутым JBoss. Теперь у вас может быть трассировка стека длиной в несколько страниц.

Возможно, вы можете опубликовать свой тестовый код?

Я провел некоторое тестирование производительности с помощью JVM 1.5, и использование исключений было как минимум на 2 раза медленнее. В среднем: время выполнения тривиально малого метода более чем в три раза (3 раза) с исключениями. Тривиально маленькая петля, которая должна была поймать исключение, увеличила время автономной работы на 2x.

Я видел аналогичные числа в производственном коде, а также в микро-тестах.

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

Например, использование «Integer.ParseInt (…)» для поиска всех плохих значений в очень большом текстовом файле – очень плохая идея. (Я видел, что этот метод утилиты убивает производительность по производственному коду)

Использование исключения для сообщения о плохом значении в форме пользовательского GUI, возможно, не так плохо с точки зрения производительности.

Независимо от того, была ли это хорошей практикой проектирования, я бы пошел с правилом: если ошибка нормальная / ожидаемая, то используйте возвращаемое значение. Если это ненормально, используйте исключение. Например: чтение пользовательских входов, неправильные значения – использование кода ошибки. Передавая значение внутренней функции утилиты, плохие значения должны быть отфильтрованы при вызове кода – используйте исключение.

Некоторое время назад я написал class, чтобы проверить относительную производительность преобразования строк в int с использованием двух подходов: (1) вызвать Integer.parseInt () и уловить исключение или (2) совместить строку с регулярным выражением и вызвать parseInt () только если совпадение выполнено успешно. Я использовал регулярное выражение наиболее эффективным образом (т. Е. Создавал объекты Pattern и Matcher до начала цикла), и я не печатал и не сохранял стеки из исключений.

Для списка из десяти тысяч строк, если все они были действительными числами, метод parseInt () был в четыре раза быстрее, чем при использовании регулярного выражения. Но если только 80% строк были действительными, регулярное выражение было в два раза быстрее parseInt (). И если 20% были действительны, то есть исключение было выброшено и поймано 80% времени, регулярное выражение было примерно в двадцать раз быстрее, чем parseInt ().

Я был удивлен результатом, считая, что подход с регулярным выражением обрабатывает действительные строки дважды: один раз для совпадения и снова для parseInt (). Но бросать и ловить исключения больше, чем компенсировать это. Такая ситуация вряд ли встречается очень часто в реальном мире, но если это так, вы определенно не должны использовать технику исключения. Но если вы только проверяете ввод пользователя или что-то в этом роде, используйте метод parseInt ().

Даже если бросать исключение не медленно, все равно неплохая идея бросить исключения для нормального streamа программы. Используется таким образом, что он аналогичен GOTO …

Наверное, это на самом деле не отвечает на вопрос. Я бы предположил, что «обычная» мудрость бросания исключений была медленной в предыдущих версиях Java (<1.4). Для создания исключения требуется, чтобы виртуальная машина создала всю трассировку стека. С тех пор многое изменилось в VM, чтобы ускорить процесс, и это, вероятно, одна из областей, которая была улучшена.

HotSpot вполне способен удалять код исключения для генерируемых системой исключений, если он все вложен. Однако явно созданное исключение и те, которые в противном случае не удаляются, тратят много времени на создание трассировки стека. Переопределите fillInStackTrace чтобы увидеть, как это может повлиять на производительность.

Производительность исключений в Java и C # оставляет желать лучшего.

Как программисты это заставляет нас жить по правилу «исключения должны быть вызваны нечасто», просто для практических соображений производительности.

Однако, как компьютерные ученые, мы должны восстать против этого проблемного состояния. Человек, создающий функцию, часто не знает, как часто он будет называться, или вероятность успеха или неудачи. Эта информация содержится только у вызывающего. Попытка избежать исключений приводит к неясным иконам API, где в некоторых случаях у нас есть только версии с чистым, но медленным исключением, а в других случаях мы имеем быстрые, но неуклюжие ошибки возвращаемого значения, а в других случаях мы заканчиваем оба , The library implementor may have to write and maintain two versions of APIs, and the caller has to decide which of two versions to use in each situation.

This is kind of a mess. If exceptions had better performance, we could avoid these clunky idioms and use exceptions as they were meant to be used… as a structured error return facility.

I’d really like to see exception mechanisms implemented using techniques closer to return-values, so we could have performance closer to return values.. since this is what we revert to in performance sensitive code.

Here is a code-sample that compares exception performance to error-return-value performance.

public class TestIt {

 int value; public int getValue() { return value; } public void reset() { value = 0; } public boolean baseline_null(boolean shouldfail, int recurse_depth) { if (recurse_depth <= 0) { return shouldfail; } else { return baseline_null(shouldfail,recurse_depth-1); } } public boolean retval_error(boolean shouldfail, int recurse_depth) { if (recurse_depth <= 0) { if (shouldfail) { return false; } else { return true; } } else { boolean nested_error = retval_error(shouldfail,recurse_depth-1); if (nested_error) { return true; } else { return false; } } } public void exception_error(boolean shouldfail, int recurse_depth) throws Exception { if (recurse_depth <= 0) { if (shouldfail) { throw new Exception(); } } else { exception_error(shouldfail,recurse_depth-1); } } public static void main(String[] args) { int i; long l; TestIt t = new TestIt(); int failures; int ITERATION_COUNT = 100000000; // (0) baseline null workload for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) { for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) { int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq); failures = 0; long start_time = System.currentTimeMillis(); t.reset(); for (i = 1; i < ITERATION_COUNT; i++) { boolean shoulderror = (i % EXCEPTION_MOD) == 0; t.baseline_null(shoulderror,recurse_depth); } long elapsed_time = System.currentTimeMillis() - start_time; System.out.format("baseline: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n", recurse_depth, exception_freq, failures,elapsed_time); } } // (1) retval_error for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) { for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) { int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq); failures = 0; long start_time = System.currentTimeMillis(); t.reset(); for (i = 1; i < ITERATION_COUNT; i++) { boolean shoulderror = (i % EXCEPTION_MOD) == 0; if (!t.retval_error(shoulderror,recurse_depth)) { failures++; } } long elapsed_time = System.currentTimeMillis() - start_time; System.out.format("retval_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n", recurse_depth, exception_freq, failures,elapsed_time); } } // (2) exception_error for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) { for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) { int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq); failures = 0; long start_time = System.currentTimeMillis(); t.reset(); for (i = 1; i < ITERATION_COUNT; i++) { boolean shoulderror = (i % EXCEPTION_MOD) == 0; try { t.exception_error(shoulderror,recurse_depth); } catch (Exception e) { failures++; } } long elapsed_time = System.currentTimeMillis() - start_time; System.out.format("exception_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n", recurse_depth, exception_freq, failures,elapsed_time); } } } 

}

And here are the results:

 baseline: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 683 ms baseline: recurse_depth 2, exception_freqeuncy 0.25 (0), time elapsed 790 ms baseline: recurse_depth 2, exception_freqeuncy 0.5 (0), time elapsed 768 ms baseline: recurse_depth 2, exception_freqeuncy 0.75 (0), time elapsed 749 ms baseline: recurse_depth 2, exception_freqeuncy 1.0 (0), time elapsed 731 ms baseline: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 923 ms baseline: recurse_depth 5, exception_freqeuncy 0.25 (0), time elapsed 971 ms baseline: recurse_depth 5, exception_freqeuncy 0.5 (0), time elapsed 982 ms baseline: recurse_depth 5, exception_freqeuncy 0.75 (0), time elapsed 947 ms baseline: recurse_depth 5, exception_freqeuncy 1.0 (0), time elapsed 937 ms baseline: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1154 ms baseline: recurse_depth 8, exception_freqeuncy 0.25 (0), time elapsed 1149 ms baseline: recurse_depth 8, exception_freqeuncy 0.5 (0), time elapsed 1133 ms baseline: recurse_depth 8, exception_freqeuncy 0.75 (0), time elapsed 1117 ms baseline: recurse_depth 8, exception_freqeuncy 1.0 (0), time elapsed 1116 ms retval_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 742 ms retval_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 743 ms retval_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 734 ms retval_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 723 ms retval_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 728 ms retval_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 920 ms retval_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 1121 ms retval_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 1037 ms retval_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 1141 ms retval_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 1130 ms retval_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1218 ms retval_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 1334 ms retval_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 1478 ms retval_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 1637 ms retval_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 1655 ms exception_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 726 ms exception_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 17487 ms exception_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 33763 ms exception_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 67367 ms exception_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 66990 ms exception_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 924 ms exception_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 23775 ms exception_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 46326 ms exception_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 91707 ms exception_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 91580 ms exception_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1144 ms exception_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 30440 ms exception_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 59116 ms exception_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 116678 ms exception_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 116477 ms 

Checking and propagating return-values does add some cost vs the baseline-null call, and that cost is proportional to call-depth. At a call-chain depth of 8, the error-return-value checking version was about 27% slower than the basline version which did not check return values.

Exception performance, in comparison, is not a function of call-depth, but of exception frequency. However, the degredation as exception frequency increases is much more dramatic. At only a 25% error frequency, the code ran 24-TIMES slower. At an error frequency of 100%, the exception version is almost 100-TIMES slower.

This suggests to me that perhaps are making the wrong tradeoffs in our exception implementations. Exceptions could be faster, either by avoiding costly stalk-walks, or by outright turning them into compiler supported return-value checking. Until they do, we're stuck avoiding them when we want our code to run fast.

Just compare let’s say Integer.parseInt to the following method, which just returns a default value in the case of unparseable data instead of throwing an Exception:

  public static int parseUnsignedInt(String s, int defaultValue) { final int strLength = s.length(); if (strLength == 0) return defaultValue; int value = 0; for (int i=strLength-1; i>=0; i--) { int c = s.charAt(i); if (c > 47 && c < 58) { c -= 48; for (int j=strLength-i; j!=1; j--) c *= 10; value += c; } else { return defaultValue; } } return value < 0 ? /* übergebener wert > Integer.MAX_VALUE? */ defaultValue : value; } 

As long as you apply both methods to “valid” data, they both will work at approximately the same rate (even although Integer.parseInt manages to handle more complex data). But as soon as you try to parse invalid data (eg to parse “abc” 1.000.000 times), the difference in performance should be essential.

I changed @Mecki ‘s answer above to have method1 return a boolean and a check in the calling method, as you cannot just replace an Exception with nothing. After two runs, method1 was still either the fastest or as fast as method2.

Here is snapshot of the code:

 // Calculates without exception public boolean method1(int i) { value = ((value + i) / i) << 1; // Will never be true return ((i & 0xFFFFFFF) == 1000000000); } .... for (i = 1; i < 100000000; i++) { if (t.method1(i)) { System.out.println("Will never be true!"); } } 

and results:

Run 1

 method1 took 841 ms, result was 2 method2 took 841 ms, result was 2 method3 took 85058 ms, result was 2 

Run 2

 method1 took 821 ms, result was 2 method2 took 838 ms, result was 2 method3 took 85929 ms, result was 2 

Great post about exception performance is:

https://shipilev.net/blog/2014/exceptional-performance/

Instantiating vs reusing existing, with stack trace and without, etc:

 Benchmark Mode Samples Mean Mean error Units dynamicException avgt 25 1901.196 14.572 ns/op dynamicException_NoStack avgt 25 67.029 0.212 ns/op dynamicException_NoStack_UsedData avgt 25 68.952 0.441 ns/op dynamicException_NoStack_UsedStack avgt 25 137.329 1.039 ns/op dynamicException_UsedData avgt 25 1900.770 9.359 ns/op dynamicException_UsedStack avgt 25 20033.658 118.600 ns/op plain avgt 25 1.259 0.002 ns/op staticException avgt 25 1.510 0.001 ns/op staticException_NoStack avgt 25 1.514 0.003 ns/op staticException_NoStack_UsedData avgt 25 4.185 0.015 ns/op staticException_NoStack_UsedStack avgt 25 19.110 0.051 ns/op staticException_UsedData avgt 25 4.159 0.007 ns/op staticException_UsedStack avgt 25 25.144 0.186 ns/op 

Depending on depth of stack trace:

 Benchmark Mode Samples Mean Mean error Units exception_0000 avgt 25 1959.068 30.783 ns/op exception_0001 avgt 25 1945.958 12.104 ns/op exception_0002 avgt 25 2063.575 47.708 ns/op exception_0004 avgt 25 2211.882 29.417 ns/op exception_0008 avgt 25 2472.729 57.336 ns/op exception_0016 avgt 25 2950.847 29.863 ns/op exception_0032 avgt 25 4416.548 50.340 ns/op exception_0064 avgt 25 6845.140 40.114 ns/op exception_0128 avgt 25 11774.758 54.299 ns/op exception_0256 avgt 25 21617.526 101.379 ns/op exception_0512 avgt 25 42780.434 144.594 ns/op exception_1024 avgt 25 82839.358 291.434 ns/op 

For other details (including x64 assembler from JIT) read original blog post.

That mean Hibernate/Spring/etc-EE-shit are slow because of exceptions (xD) and rewriting app control flow away from exceptions (replace it with continure / break and returning boolean flags like in C from method call) improve performance of your application 10x-100x, depending on how often you throws them ))

My opinion about Exception speed versus checking data programmatically.

Many classes had String to value converter (scanner / parser), respected and well-known libraries too 😉

usually has form

 class Example { public static Example Parse(String input) throws AnyRuntimeParsigException ... } 

exception name is only example, usually is unchecked (runtime), so throws declaration is only my picture

sometimes exist second form:

 public static Example Parse(String input, Example defaultValue) 

never throwing

When the second ins’t available (or programmer read too less docs and use only first), write such code with regular expression. Regular expression are cool, politically correct etc:

 Xxxxx.regex(".....pattern", src); if(ImTotallySure) { Example v = Example.Parse(src); } 

with this code programmers hasn’t cost of exceptions. BUT HAS comparable very HIGH cost of regular expressions ALWAYS versus small cost of exception sometimes.

I use almost always in such context

 try { parse } catch(ParsingException ) // concrete exception from javadoc { } 

without analysing stacktrace etc, I believe after lectures of Yours quite speed.

Do not be afraid Exceptions

Why should exceptions be any slower than normal returns?

As long as you don’t print the stacktrace to the terminal, save it into a file or something similar, the catch-block doesn’t do any more work than other code-blocks. So, I can’t imagine why “throw new my_cool_error()” should be that slow.

Good question and I’m looking forward to further information on this topic!

  • Исключение исключения: деление на ноль
  • Исключение в streamе "main" java.io.FileNotFoundException: Ошибка
  • Разница между try / catch / throw и try / catch (e) / throw e
  • Когда я должен действительно использовать noexcept?
  • Должны ли вы сообщать текст сообщений об исключениях?
  • Node.js Обработка исключений при передовом опыте
  • VS2010 не показывает необработанное сообщение об исключении в приложении WinForms в 64-разрядной версии Windows
  • Настройка приоритета нескольких @ControllerAdvice @ExceptionHandlers
  • Что на самом деле происходит в try {return x; } наконец {x = null; } заявление?
  • Как проверить действительный xml в строковом вводе перед вызовом .LoadXml ()
  • Блоки catch C ++ - исключение catch по значению или ссылке?
  • Давайте будем гением компьютера.