Насколько медленны исключения Java?
Вопрос: Обработка исключений в Java на самом деле медленная?
Обычная мудрость, а также множество результатов Google говорят, что исключительная логика не должна использоваться для нормального потока программ в Java. Обычно приводятся две причины:
- он действительно медленный - даже на порядок медленнее обычного кода (приведенные причины варьируются),
а также
- это грязно, потому что люди ожидают, что только ошибки будут обработаны в исключительном коде.
Этот вопрос касается №1.
В качестве примера эта страница описывает обработку исключений Java как "очень медленную" и связывает медленность с созданием строки сообщения об исключении - "эта строка затем используется при создании объекта исключения, который вызывается. Это не быстро". В статье " Эффективная обработка исключений в Java" говорится, что "причина этого связана с аспектом создания объекта обработки исключений, что делает тем самым исключение метаданных исключениями". Другая причина в том, что генерация трассировки стека - это то, что замедляет ее.
Мое тестирование (с использованием Java 1.6.0_07, Java HotSpot 10.0, на 32-разрядном Linux) указывает, что обработка исключений не медленнее обычного кода. Я попытался запустить метод в цикле, который выполняет некоторый код. В конце метода я использую логическое значение, чтобы указать, следует ли возвращать или бросать. Таким образом, фактическая обработка одинакова. Я пытался использовать методы в разных порядках и усреднять время тестирования, думая, что это, возможно, прогревало JVM. Во всех моих тестах бросок был как минимум быстрым, чем возврат, если не быстрее (до 3,1% быстрее). Я полностью открыт для возможности того, что мои тесты были неправильными, но я не видел ничего на пути к образцу кода, сравнению тестов или результатов за последний год или два, которые показывают обработку исключений в Java, на самом деле медленный.
Что привело меня по этому пути, я использовал API, который мне нужен, чтобы использовать исключения, которые являются частью обычной логики управления. Я хотел исправить их в их использовании, но теперь я, возможно, не смогу. Будут ли я вместо этого хвалить их на их будущее мышление?
В статье " Эффективная обработка исключений Java" в компиляции "точно вовремя" авторы полагают, что наличие только обработчиков исключений, даже если исключений не было, достаточно, чтобы компилятор JIT не смог правильно оптимизировать код, тем самым замедляя его, Я еще не тестировал эту теорию.
Ответы
Ответ 1
Это зависит от того, как реализованы исключения. Самый простой способ - использовать setjmp и longjmp. Это означает, что все регистры процессора записываются в стек (который уже занимает некоторое время), и, возможно, необходимо создать некоторые другие данные... все это уже происходит в инструкции try. Оператор throw должен разворачивать стек и восстанавливать значения всех регистров (и возможных других значений в виртуальной машине). Таким образом, попытка и бросок одинаково медленны, и это довольно медленно, однако, если исключение не выбрасывается, выход из блока try в большинстве случаев не занимает времени (так как все помещается в стек, который автоматически очищается, если метод существует).
Sun и другие признали, что это возможно субоптимально, и, разумеется, с течением времени виртуальные машины становятся все быстрее и быстрее. Существует еще один способ реализации исключений, который заставляет попробовать себя молниеносно (на самом деле ничего не происходит для попытки вообще вообще - все, что должно произойти, уже выполняется, когда класс загружается виртуальной машиной), и это делает не совсем медленным, Я не знаю, какая JVM использует эту новую, лучшую технику...
... но вы пишете на Java, поэтому ваш код позже работает только на одной JVM на одной конкретной системе? Поскольку, если он может когда-либо работать на любой другой платформе или любой другой версии JVM (возможно, любого другого поставщика), кто говорит, что они также используют быструю реализацию? Быстрая, более сложная, чем медленная, и не всегда возможна во всех системах. Вы хотите оставаться портативным? Тогда не полагайтесь на быстрые исключения.
Это также сильно влияет на то, что вы делаете в блоке try. Если вы откроете блок try и никогда не вызываете какой-либо метод из этого блока try, блок try будет очень быстрым, так как JIT может тогда обработать бросок, как простой goto. Не нужно сохранять состояние стека и не нужно разматывать стек, если выбрано исключение (ему нужно только перейти к обработчикам catch). Однако это не то, что вы обычно делаете. Обычно вы открываете блок try, а затем вызываете метод, который может генерировать исключение, не так ли? И даже если вы просто используете блок try в своем методе, каким будет метод, который не вызывает никакого другого метода? Будет ли он просто рассчитать число? Тогда зачем вам нужны исключения? Есть гораздо более элегантные способы регулирования потока программы. Для почти ничего, кроме простой математики, вам придется вызвать внешний метод, и это уже разрушает преимущество локального блока 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.
Ответ 2
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 большая часть затрат на выброс исключения - это время, затрачиваемое на сбор трассировки стека, которая возникает при создании объекта исключения. Фактическая стоимость выброса исключения, в то время как большая, значительно меньше стоимости создания исключения.
Ответ 3
Алексей Шипилев провел очень тщательный анализ, в котором он оценивает исключения Java при различных сочетаниях условий:
- Недавно созданные исключения против предварительно созданных исключений
- Трассировка стека против отключенной
- Запрошенная трассировка стека vs никогда не запрашивалась
- Пойманный на высшем уровне против восстания на каждом уровне против прикованного/завернутого на каждом уровне
- Различные уровни глубины стека Java-вызовов
- Отсутствие оптимизаций наложения и экстремальных вложений против настроек по умолчанию
- Поля, определяемые пользователем, прочитаны, а не прочитаны
Он также сравнивает их с эффективностью проверки кода ошибки на разных уровнях частоты ошибок.
Выводы (цитируемые дословно из его поста) были следующими:
-
Поистине исключительные исключения прекрасно исполняются. Если вы используете их как разработанные и обмениваетесь исключительно исключительными случаями среди подавляющего большинства не исключительных случаев, обрабатываемых обычным кодом, то использование исключений - это победа в производительности.
-
Затраты на выполнение исключений включают два основных компонента: построение трассировки стека при создании экземпляра Exception и разворачивание стека во время броска исключения.
-
Стоимость строительства трассировки стека пропорциональна глубине стека в момент создания экземпляра исключения. Это уже плохо, потому что кто на Земле знает глубину стека, на которой будет называться этот метод метания? Даже если вы отключите генерацию трассировки стека и/или кешируете исключения, вы можете избавиться только от этой части стоимости исполнения.
-
Расходы на разворачивание стека зависят от того, как нам повезло с тем, что обработчик исключений ближе к компилированному коду. Тщательное структурирование кода, чтобы избежать поиска обработчиков глубоких исключений, вероятно, помогает нам стать счастливее.
-
Если мы устраним оба эффекта, стоимость выполнения исключений - это стоимость локального ветки. Независимо от того, насколько красиво это звучит, это не означает, что вы должны использовать Exceptions как обычный поток управления, потому что в этом случае вы во власти оптимизации компилятора! Вы должны использовать их только в действительно исключительных случаях, когда частота исключений амортизирует возможную неудачную стоимость повышения фактического исключения.
-
Оптимистическое правило большого пальца, похоже, является частотой 10 ^ -4 для исключений, является достаточно исключительным. Это, конечно, зависит от тяжелых весов самих исключений, точных действий, предпринятых в обработчиках исключений и т.д.
Результатом является то, что когда исключение не выбрасывается, вы не платите стоимость, поэтому, когда исключительное условие является достаточно редким, обработка исключений выполняется быстрее, чем при использовании if
каждый раз. Полный пост очень стоит прочитать.
Ответ 4
Мой ответ, к сожалению, слишком длинный, чтобы публиковать здесь. Поэтому давайте подытожим здесь и перейдем к http://www.fuwjax.com/how-slow-are-java-exceptions/ для подробных подробностей.
Реальный вопрос здесь заключается не в том, "насколько медленными являются" неудачи ", отмеченные как исключения, по сравнению с" кодом, который никогда не сработает "?" поскольку принятый ответ мог бы вам поверить. Вместо этого возникает вопрос: "Насколько медленными являются" неудачи, сообщаемые как исключения ", по сравнению с отказами, сообщенными другими способами?" Как правило, два других способа сообщения об ошибках - либо с дозорными значениями, либо с помощью оберток результатов.
Значения Sentinel - это попытка вернуть один класс в случае успеха, а другой - в случае сбоя. Вы можете думать об этом почти как о возвращении исключения вместо того, чтобы его бросать. Для этого требуется общий родительский класс с объектом успеха, а затем выполнить проверку "instanceof" и пару бросков, чтобы получить информацию об успехе или ошибке.
Оказывается, что с риском безопасности типов значения Sentinel быстрее, чем исключения, но только в два раза. Теперь это может показаться много, но это 2x покрывает стоимость разницы в реализации. На практике коэффициент намного ниже, поскольку наши методы, которые могут быть неудачными, гораздо интереснее, чем несколько арифметических операторов, как в примере кода в другом месте на этой странице.
С другой стороны, Wrappers не жертвуют безопасностью типа. Они обертывают информацию об успехах и сбоях в одном классе. Поэтому вместо "instanceof" они предоставляют "isSuccess()" и getters как для объектов успеха, так и для отказа. Однако объекты результатов примерно в 2 раза медленнее, чем использование исключений. Оказывается, что создание нового объекта-оболочки каждый раз намного дороже, чем иногда возникает исключение.
Кроме того, исключения - это язык, на котором указывается способ отказа метода. Нет другого способа рассказать только из API, какие методы должны всегда (в основном) работать и которые, как ожидается, сообщают о сбое.
Исключения более безопасны, чем часовые, быстрее, чем объекты результата, и менее удивительны, чем другие. Я не предлагаю, чтобы try/catch заменили if/else, но исключения - это правильный способ сообщить об ошибке, даже в бизнес-логике.
Тем не менее, я хотел бы отметить, что два наиболее частых способа существенного влияния на производительность, с которыми я столкнулся, создают ненужные объекты и вложенные циклы. Если у вас есть выбор между созданием исключения или не созданием исключения, не создавайте исключение. Если у вас есть выбор между созданием исключения или созданием другого объекта все время, создайте исключение.
Ответ 5
Я расширяю ответы, заданные @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, используя только типы возвращаемых данных для потоков успеха и ошибок. Методы, которые вызывают исключения без stacktrace, в 4-5 раз быстрее, чем обычные Исключения.
Редактировать: NoStackTraceThrowable.java Спасибо @Greg
public class NoStackTraceThrowable extends Throwable {
public NoStackTraceThrowable() {
super("my special throwable", null, false, false);
}
}
Ответ 6
Я думаю, что первая статья относится к акту прохождения стека вызовов и созданию трассировки стека как дорогостоящей части, а вторая статья не говорит об этом, я думаю, что это самая дорогая часть создания объекта, Джон Роуз статью, где он описывает различные методы ускорения исключений. (Предопределение и повторное использование исключения, исключений без следов стека и т.д.)
Но все же - я думаю, это следует рассматривать только как необходимое зло, последнее средство. Джон объясняет это тем, что эмулировать функции на других языках, которые пока недоступны в JVM. Вы не должны привыкать к использованию исключений для потока управления. Особенно не по соображениям производительности! Как вы сами упоминаете в № 2, вы рискуете маскировать серьезные ошибки в своем коде таким образом, и это будет сложнее поддерживать для новых программистов.
Микрообъекты в Java на удивление трудно получить право (я сказал), особенно когда вы попадаете на территорию JIT, поэтому я действительно сомневаюсь, что использование исключений происходит быстрее, чем "возвращение" в реальной жизни. Например, я подозреваю, что в вашем тесте у вас есть от 2 до 5 кадров стека? Теперь представьте, что ваш код будет вызываться компонентом JSF, развернутым JBoss. Теперь у вас может быть трассировка стека длиной в несколько страниц.
Возможно, вы можете опубликовать свой тестовый код?
Ответ 7
Не знаю, связаны ли эти темы, но однажды я хотел реализовать один трюк, полагающийся на текущую трассировку стека потока: я хотел узнать имя метода, который вызвал экземпляр внутри экземпляра класса (yeap, идея сумасшедший, я полностью отказался от него). Поэтому я обнаружил, что вызов Thread.currentThread().getStackTrace()
медленнее (из-за встроенного метода dumpThreads
, который он использует внутри).
Итак, Java Throwable
, соответственно, имеет собственный метод fillInStackTrace
. Я думаю, что описанный ранее блок killer-catch
каким-то образом запускает выполнение этого метода.
Но позвольте мне рассказать вам еще одну историю...
В 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
заключается в том, что они генерируют разные виды исключений. Yeap, method4
все еще медленнее, чем method1
и method2
, но разница намного более приемлема.
Ответ 8
Я провел некоторое тестирование производительности с помощью JVM 1.5, а использование исключений было, по крайней мере, в 2 раза медленнее. В среднем: время выполнения тривиально малого метода более чем в три раза (3 раза) с исключениями. Тривиально маленькая петля, которая должна была поймать исключение, увеличила время автономной работы на 2x.
Я видел аналогичные числа в производственном коде, а также в микро-тестах.
Исключения должны определяться НЕ для частого вызова. Бросив тысячи исключений, вторая могла бы вызвать огромную горло бутылки.
Например, использование "Integer.ParseInt(...)" для поиска всех плохих значений в очень большом текстовом файле - очень плохая идея. (Я видел, что этот метод утилиты убивает производительность на производственном коде)
Использование исключения для сообщения о плохом значении в форме пользовательского GUI, возможно, не так плохо с точки зрения производительности.
Независимо от того, хорошо ли это при разработке дизайна, я бы пошел с правилом: если ошибка нормальная/ожидаемая, используйте возвращаемое значение. Если это ненормально, используйте исключение. Например: чтение пользовательских входов, неправильные значения - использование кода ошибки. Передавая значение внутренней функции утилиты, плохие значения должны быть отфильтрованы по вызову кода - используйте исключение.
Ответ 9
A назад я написал класс для проверки относительной производительности преобразования строк в int с использованием двух подходов: (1) вызвать Integer.parseInt() и уловить исключение или (2) совместить строку с регулярным выражением и вызовом parseInt(), только если совпадение выполнено успешно. Я использовал регулярное выражение наиболее эффективным способом, который мог бы (то есть создать объекты Pattern и Matcher до начала цикла), и я не печатал и не сохранял стеки из исключений.
Для списка из десяти тысяч строк, если все они были действительными числами, метод parseInt() был в четыре раза быстрее, чем при использовании регулярного выражения. Но если только 80% строк были действительными, регулярное выражение было в два раза быстрее parseInt(). И если 20% были действительными, то есть исключение было выброшено и поймано 80% времени, регулярное выражение было примерно в двадцать раз быстрее, чем parseInt().
Я был удивлен результатом, считая, что подход с регулярным выражением обрабатывает действительные строки дважды: один раз для совпадения и снова для parseInt(). Но бросать и ловить исключения больше, чем компенсировать это. Такая ситуация вряд ли встречается очень часто в реальном мире, но если это так, вы определенно не должны использовать технику исключения. Но если вы только проверяете ввод пользователя или что-то в этом роде, обязательно используйте подход parseInt().
Ответ 10
Даже если бросать исключение не медленно, все равно неплохая идея бросить исключения для нормального потока программы. Используется таким образом, что он аналогичен GOTO...
Я предполагаю, что на самом деле это не отвечает на вопрос. Я бы предположил, что "обычная" мудрость бросания исключений была медленной, было верно в более ранних версиях java (< 1.4). Для создания исключения требуется, чтобы виртуальная машина создала всю трассировку стека. С тех пор многое изменилось в VM, чтобы ускорить процесс, и это, вероятно, одна из областей, которые были улучшены.
Ответ 11
HotSpot вполне способен удалять код исключения для генерируемых системой исключений, если он все вложен. Однако явно созданное исключение и те, которые в противном случае не удаляются, тратят много времени на создание трассировки стека. Переопределите fillInStackTrace
, чтобы увидеть, как это может повлиять на производительность.
Ответ 12
Производительность исключения в Java и С# оставляет желать лучшего.
Как программисты заставляют нас жить по правилу "исключения должны быть вызваны нечасто", просто по практическим соображениям производительности.
Однако, как компьютерные ученые, мы должны восстать против этого проблемного состояния. Человек, создающий функцию, часто не знает, как часто он будет называться, или вероятность успеха или неудачи. Эта информация содержится только у вызывающего. Попытка избежать исключений приводит к неясным иконам API, где в некоторых случаях у нас есть только версии с чистым, но медленным исключением, а в других случаях мы имеем быстрые, но неуклюжие ошибки возвращаемого значения, а в других случаях мы заканчиваем оба, Разработчику библиотеки, возможно, придется писать и поддерживать две версии API, и вызывающий должен решить, какую из двух версий использовать в каждой ситуации.
Это своего рода беспорядок. Если бы исключения имели лучшую производительность, мы могли бы избежать этих неуклюжих идиом и использовать исключения, поскольку они предназначались для использования... как средство структурированного возврата ошибок.
Мне бы очень хотелось, чтобы механизмы исключений были реализованы с использованием методов ближе к значениям return-значений, поэтому мы могли бы повысить производительность по отношению к возвращаемым значениям.. так как это то, к чему мы возвращаемся в коде с высокой степенью чувствительности.
Вот пример кода, который сравнивает производительность исключений с производительностью с ошибкой-возвратом.
открытый класс 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);
}
}
}
}
И вот результаты:
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
Проверка и распространение возвращаемых значений добавляет некоторую стоимость по сравнению с вызовом базовой линии-null, и эта стоимость пропорциональна глубине вызова. При глубине вызова цепочки 8 версия проверки на возврат ошибки была примерно на 27% медленнее, чем версия базовой линии, которая не проверяла возвращаемые значения.
Производительность исключений, в сравнении, не является функцией глубины вызова, а частоты исключения. Тем не менее, деградация как увеличение частоты исключений намного более драматична. При частоте ошибки 25% код работал на 24-TIMES медленнее. При частоте ошибки 100% версия исключения почти на 100 раз медленнее.
Это говорит мне, что, возможно, ошибочные компромиссы в наших реализациях исключений. Исключения могут быть более быстрыми, либо избегая дорогостоящих прогулок по стеблям, либо просто превращая их в проверку возвращаемого значения, поддерживаемую компилятором. Пока они этого не делают, мы избегаем их, когда хотим, чтобы наш код работал быстро.
Ответ 13
Просто сравните let say Integer.parseInt со следующим методом, который просто возвращает значение по умолчанию в случае непассивных данных вместо того, чтобы выбрасывать исключение:
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;
}
Пока вы применяете оба метода к "достоверным" данным, они оба будут работать примерно с одинаковой скоростью (даже если Integer.parseInt справляется с более сложными данными). Но как только вы попытаетесь проанализировать недопустимые данные (например, для анализа "abc" 1.000.000 раз), разница в производительности должна быть существенной.
Ответ 14
Я изменил @Mecki ответ выше, чтобы метод1 возвращал логическое значение и проверял вызывающий метод, поскольку вы не можете просто заменить исключение ничем. После двух прогонов метод 1 был либо самым быстрым, либо быстрым, как метод2.
Вот моментальный снимок кода:
// 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!");
}
}
и результаты:
Выполнить 1
method1 took 841 ms, result was 2
method2 took 841 ms, result was 2
method3 took 85058 ms, result was 2
Выполнить 2
method1 took 821 ms, result was 2
method2 took 838 ms, result was 2
method3 took 85929 ms, result was 2
Ответ 15
Отличная статья об эффективности исключения:
https://shipilev.net/blog/2014/exceptional-performance/
Создание и повторное использование существующих, с трассировкой стека и без, и т. Д.:
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
В зависимости от глубины трассировки стека:
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
Для получения дополнительной информации (включая ассемблер x64 от JIT) прочитайте исходное сообщение в блоге.
Это означает, что Hibernate/Spring/etc-EE-shit медленны из-за исключений (xD) и переписывают управление приложениями от исключений (заменяют его на continure
/break
и возвращают boolean
флаги, такие как C из вызова метода), улучшают производительность вашего приложения 10x-100x, в зависимости от того, как часто вы их бросаете))
Ответ 16
Мое мнение о скорости исключения по сравнению с проверкой данных программно.
Многие классы имели String для конвертера значений (сканер/парсер), уважаемые и хорошо известные библиотеки;)
обычно имеет форму
class Example {
public static Example Parse(String input) throws AnyRuntimeParsigException
...
}
имя исключения - только пример, обычно не отмечен (runtime), поэтому объявление throw - это только мое изображение
иногда существуют вторая форма:
public static Example Parse(String input, Example defaultValue)
никогда не бросать
Когда второй не доступен (или программист читает слишком меньше документов и использует только первый), напишите такой код с регулярным выражением. Регулярное выражение - это здорово, политически корректно и т.д.
Xxxxx.regex(".....pattern", src);
if(ImTotallySure)
{
Example v = Example.Parse(src);
}
с помощью этого кода программисты не имеют стоимости исключений. НО СУЩЕСТВУЕТ сравнимую очень высокую стоимость регулярных выражений ВСЕГДА или небольшую стоимость исключения иногда.
Я использую почти всегда в таком контексте
try { parse } catch(ParsingException ) // concrete exception from javadoc
{
}
не анализируя stacktrace и т.д., я считаю, что после ваших лекций достаточно быстро.
Не бойтесь Исключения
Ответ 17
Почему исключения должны быть медленнее, чем обычные?
Пока вы не печатаете stacktrace на терминал, сохраните его в файл или что-то подобное, блок catch не будет работать больше, чем другие кодовые блоки. Итак, я не могу себе представить, почему "throw new my_cool_error()" должен быть таким медленным.
Хороший вопрос, и я с нетерпением жду дополнительной информации по этой теме!