Каково влияние исключений на производительность в Java?


496

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

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

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

а также

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

Этот вопрос о № 1.

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

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

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

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


8
Я знаю, что вы не спрашивали о 2), но вы действительно должны признать, что использование исключения для выполнения программы не лучше, чем использование GOTO. Некоторые люди защищают gotos, некоторые будут защищать то, о чем вы говорите, но если вы спросите кого-то, кто внедрил и обслуживал какое-то время, они скажут вам, что оба плохо справляются с практикой проектирования (и, вероятно, будут ругаться имя человека, который думал, что он достаточно умен, чтобы принять решение использовать их).
Билл К

80
Билл, утверждая, что использование исключений для выполнения программы не лучше, чем использование GOTO, не лучше, чем утверждение, что использование условий и циклов для выполнения программы не лучше, чем использование GOTO. Это красная сельдь. Объяснись. Исключения могут и эффективно используются для выполнения программ на других языках. Например, идиоматический код Python регулярно использует исключения. Я могу и поддерживал код, который использует исключения таким образом (но не Java), и я не думаю, что с этим что-то не так.
2010 года

14
@mmalone, использующий исключения для нормального потока управления, является плохой идеей в Java, потому что выбор парадигмы был сделан именно таким образом . Прочитайте Блох EJ2 - он четко заявляет, что, цитируя (пункт 57), exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow- дает полное и подробное объяснение, почему. И он был парнем, который написал Java lib. Поэтому именно он определяет контракт API для классов. / согласен Билл К на этом.

8
@ OndraŽižka Если какой-то фреймворк делает это (использует Исключения в неисключительном состоянии), он ошибочен и нарушен по замыслу, нарушая контракт класса Exception языка. То, что некоторые люди пишут паршивый код, не делает его менее паршивым.

8
Никто, кроме создателя stackoverflow.com, не прав насчет исключений. Золотое правило разработки программного обеспечения никогда не делает простое сложным и громоздким. Он пишет: «Это правда, что то, что должно быть простой трехстрочной программой, часто вырастает до 48 строк, когда вы проводите хорошую проверку ошибок, но это жизнь ...» Это поиск чистоты, а не простоты.
sf_jeff

Ответы:


345

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

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

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

Это также имеет большое значение для того, что вы делаете в блоке try. Если вы открываете блок try и никогда не вызываете какой-либо метод из этого блока try, блок try будет очень быстрым, поскольку JIT может фактически обработать бросок как простое goto. Ему не нужно ни сохранять состояние стека, ни разматывать стек, если выдается исключение (ему нужно только перейти к обработчикам перехвата). Тем не менее, это не то, что вы обычно делаете. Обычно вы открываете блок 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 и throw все в один и тот же метод (method3), но это специальная оптимизация JIT, на которую я бы не рассчитывал. И даже при использовании этой оптимизации бросок все еще довольно медленный. Так что я не знаю, что вы пытаетесь сделать здесь, но определенно есть лучший способ сделать это, чем использовать try / catch / throw.


7
Отличный ответ, но я просто хотел бы добавить, что насколько я знаю, System.nanoTime () должен использоваться для измерения производительности, а не System.currentTimeMillis ().
Саймон Форсберг

10
@ SimonAndréForsberg nanoTime()требует Java 1.5, и в моей системе была доступна только Java 1.4, которую я использовал для написания кода выше. Также это не играет огромной роли на практике. Единственное различие между ними состоит в том, что одна составляет наносекунду, а другая - миллисекунды, и на нее nanoTimeне влияют манипуляции с часами (которые не имеют значения, если вы или системный процесс не изменяете системные часы именно в тот момент, когда выполняется тестовый код). В целом вы правы, хотя, nanoTimeконечно, лучший выбор.
Меки

2
Стоит отметить, что ваш тест - крайний случай. Вы показываете очень малую производительность для кода с tryблоком, но нет throw. Ваш throwтест бросающее исключения 50% времени она проходит через try. Это явно ситуация, когда неудача не является исключительной . Сокращение этого показателя до 10% приводит к значительному снижению производительности. Проблема с этим типом теста заключается в том, что он поощряет людей вообще отказываться от использования исключений. Использование исключений для исключительной обработки обращений работает намного лучше, чем показывает ваш тест.
Nate

1
@Nate Прежде всего, я очень четко сказал, что все это зависит от того, как реализованы исключения. Я только что тестировал ОДНУ конкретную реализацию, но есть много, и Oracle может выбрать совершенно другую реализацию с каждым выпуском. Во-вторых, если исключения являются исключительно исключительными, то, чем они обычно являются, конечно, влияние меньше, это настолько очевидно, что я действительно не думаю, что нужно прямо указывать на это, и поэтому я вообще не могу понять вашу точку зрения здесь. И в-третьих, исключения злоупотребляют этим плохо, все с этим согласны, поэтому очень осторожно использовать их.
Меки

4
@ Glide Бросок не похож на чистый return. Он оставляет метод где-то посередине тела, может быть, даже в середине операции (которая на данный момент завершена только на 50%), и catchблок может быть на 20 кадров стека вверх (у метода есть tryблок, вызывающий method1, который вызывает method2, который вызывает mehtod3, ..., а в method20 в середине операции выдается исключение). Стек должен быть размотан на 20 кадров вверх, все незавершенные операции должны быть отменены (операции не должны выполняться наполовину), а регистры ЦП должны находиться в чистом состоянии. Это все требует времени.
Меки

256

К вашему сведению, я продлил эксперимент, который провел Меки:

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 такие же, как у Меки (мой ноутбук явно медленнее).

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

method5 похож на method3, за исключением того, что он создает, new Exception()не бросая его.

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

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


49
+1 Ваш ответ решает основную проблему - время, затрачиваемое на раскручивание стека и трассировку стека, и, во-вторых, сброс ошибки. Я бы выбрал это как окончательный ответ.
инженер

9
отлично. ~ 70% создают исключение, ~ 30% бросают его. хорошая информация
чакке

1
@Basil - Вы должны быть в состоянии понять это из приведенных выше чисел.
Hot Licks

2
@HotLicks, и именно поэтому важно сказать, какая версия Java использовалась в посте
Thorbjørn Ravn Andersen

3
Мы можем заметить, что в стандартном коде создание и создание исключений происходит в редких случаях (я имею в виду во время выполнения), если это не так, либо условия выполнения очень плохие, либо сам дизайн - проблема; в обоих случаях выступления не являются проблемой ...
Жан-Батист Юнэс

70

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

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

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

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

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

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

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

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

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

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

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


41

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

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

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

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

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

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

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

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


5
Я решил протестировать долгосрочную производительность трех реализаций по сравнению с контрольной реализацией, которая проверяет наличие ошибок без отчетов. Процесс имеет частоту отказов около 4%. Итерация теста вызывает процесс 10000 раз против одной из стратегий. Каждая стратегия проверяется 1000 раз, а последние 900 раз используются для генерации статистики. Вот среднее время в наносах: Контроль 338 Исключение 429 Результат 348 Сентинел 345
Fuwjax

2
Просто для удовольствия я отключил fillInStackTrace в тесте исключений. Сейчас время: Control 347 Exception 351 Результат 364 Sentinel 355
Fuwjax

1
Fuwjax, если я что-то упустил (и я признаю, что прочитал только твой SO-пост, а не твой пост в блоге), кажется, что твои два комментария выше противоречат твоему посту. Я предполагаю, что меньшие числа лучше в вашем тесте, верно? В этом случае генерация исключений с включенной функцией fillInStackTrace (которая является поведением по умолчанию и обычным поведением) приводит к более низкой производительности, чем две другие описанные вами методики. Я что-то упустил, или вы действительно прокомментировали, чтобы опровергнуть ваш пост?
Феликс Г.В.

@Fuwjax - способ избежать выбора «камня и наковальни», который вы здесь представляете, - это предварительно выделить объект, который представляет «успех». Обычно можно также предварительно выделить объекты для общих случаев отказа. Тогда только в редком случае передачи дополнительной детализации создается новый объект. (Это OO-эквивалент целочисленных «кодов ошибок» плюс отдельный вызов для получения сведений о последней ошибке - методике, существовавшей десятилетиями.)
ToolmakerSteve

@Fuwjax То есть создание исключения не создает объект в вашей учетной записи? Не уверен, что понимаю эти рассуждения. Независимо от того, генерируете ли вы исключение или возвращаете объект результата, вы создаете объекты. В этом смысле объекты результата не медленнее, чем выбрасывание исключения.
Матиас

20

Я расширил ответы, данные @Mecki и @incarnate , без заполнения трассировки стека для 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

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

Изменить: NoStackTraceThrowable.java Спасибо @Greg

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

интересно, спасибо. Вот отсутствующее объявление класса:public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } }
Грег

Вы написали, With Java 7+, we can useно позже написали, Output with Java 1.6.0_45,что это результат Java 6 или 7?
WBAR

1
@WBAR из Java 7, нам просто нужно использовать Throwableконструктор с boolean writableStackTraceаргументом arg. Но этого нет в Java 6 и ниже. Вот почему я дал пользовательскую реализацию для Java 6 и ниже. Таким образом, приведенный выше код предназначен для Java 6 и ниже. Пожалуйста, внимательно прочитайте первую строчку второго абзаца.
Manikanta

@manikanta «ИМХО, мы не можем спроектировать понятный API, просто используя типы возвращаемых значений как для потоков успеха, так и для ошибок.» - мы можем, если будем использовать Optionals / Results / Может быть, как это делают многие языки.
Hejazzman

@ Hejazzman Я согласен. Но Optionalили подобное пришло с запозданием на Java. До этого мы также использовали объекты-оболочки с флагами успеха / ошибки. Но это, кажется, немного взломано и не кажется мне естественным.
Manikanta

8

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

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

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


какую JVM вы использовали? Это все еще так медленно с Sun-JDK 6?
Бенедикт Вальдвогель

Я откопал его и снова запустил под JDK 1.6u10, прежде чем отправить этот ответ, и вот те результаты, которые я опубликовал.
Алан Мур

Это очень, очень полезно! Спасибо. Для моих обычных случаев использования мне нужно проанализировать пользовательские вводы (используя что-то подобное Integer.ParseInt()), и я ожидаю, что в большинстве случаев пользовательский ввод будет правильным, поэтому для моего варианта использования кажется, что случайное попадание исключения - это путь ,
markvgti

8

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

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

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

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


7

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

Так что у Java Throwable, соответственно, есть нативный метод fillInStackTrace. Я думаю, что киллер- 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том, что они бросают различные виды исключений. Да, method4все еще медленнее, чем method1и method2, но разница гораздо более приемлема.


6

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

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

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

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

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

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


Позвольте мне предложить некоторые вещи, которые хорошо сделать: если вам нужно число в форме, вместо использования Integer.valueOf (String), вам следует рассмотреть возможность использования сопоставителя регулярного выражения. Вы можете предварительно скомпилировать и повторно использовать шаблон, чтобы сделать сопоставления дешевыми. Однако в форме графического интерфейса наличие isValid / validate / checkField или того, что у вас, возможно, более понятно. Кроме того, в Java 8 у нас есть дополнительные монады, поэтому рассмотрите возможность их использования. (ответ 9 лет, но все же!: p)
Хакон Лотвейт

4

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

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

Однако, как компьютерные ученые, мы должны восстать против этого проблемного состояния. Человек, создающий функцию, часто не знает, как часто она будет вызываться, или более вероятен успех или неудача. Только звонящий имеет эту информацию. Попытка избежать исключений приводит к неясным идентификаторам API, где в некоторых случаях у нас есть только чистые, но медленные версии исключений, а в других случаях у нас бывают быстрые, но неуклюжие ошибки возвращаемого значения, а в других случаях мы получаем оба варианта: , Разработчику библиотеки, возможно, придется написать и поддерживать две версии API, и вызывающая сторона должна решить, какую из двух версий использовать в каждой ситуации.

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

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

Вот пример кода, который сравнивает производительность исключения с производительностью возвращаемого значения ошибки.

открытый класс 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

Проверка и распространение возвращаемых значений действительно добавляет некоторую стоимость к вызову baseline-null, и эта стоимость пропорциональна глубине вызова. При глубине цепочки вызовов, равной 8, версия проверки возвращаемого значения ошибки была примерно на 27% медленнее базовой версии, которая не проверяла возвращаемые значения.

В отличие от этого, эффективность исключений зависит не от глубины вызова, а от частоты исключений. Однако деградация при увеличении частоты исключений гораздо более драматична. Только с частотой ошибок 25% код работал в 24 раза медленнее. При частоте ошибок 100% версия исключения почти в 100 раз медленнее.

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


3

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


2

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

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


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

2
@ Спенсер К. Исключение, как следует из названия, означает, что была обнаружена исключительная ситуация (файл исчез, сеть внезапно закрылась, ...). Это подразумевает, что ситуация была НЕОЖИДАНА. Если ОЖИДАЕТСЯ, что ситуация произойдет, я бы не стал использовать исключение для нее.
Меки

2
@ Меки: верно. Недавно у меня была с кем-то дискуссия по этому поводу ... Они писали среду валидации и выдавали исключение в случае неудачи валидации. Я думаю, что это плохая идея, так как это было бы довольно распространенным явлением. Я бы предпочел, чтобы метод возвращал ValidationResult.
user38051

2
С точки зрения потока управления, исключение аналогично breakили return, а не goto.
Hot Licks

3
Существует множество парадигм программирования. Не может быть ни одного «нормального потока», что бы вы ни подразумевали под этим. По сути, механизм исключений - это просто способ быстро покинуть текущий кадр и размотать стек до определенной точки. Слово «исключение» ничего не говорит о его «неожиданной» природе. Быстрый пример: очень естественно «выбрасывать» 404 из веб-приложений, когда на пути маршрутизации возникают определенные обстоятельства. Почему эта логика не будет реализована с исключениями? Что такое анти-паттерн?
Воплощение

2

Просто сравните, скажем, 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 раз), разница в производительности должна быть существенной.


2

Отличный пост о выполнении исключений:

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 из вызова метода), чтобы повысить производительность вашего приложения в 10-100 раз в зависимости от того, как часто вы их кидаете))


0

Я изменил ответ @Mecki выше, чтобы method1 возвращал логическое значение и проверку в вызывающем методе, поскольку вы не можете просто заменить Exception ничем. После двух запусков метод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

Run 2

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

0

Исключение предназначено для обработки неожиданных условий во время выполнения только.

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

Вызов исключения вместо простой проверки if..else также сделает код сложным для написания и обслуживания.


-3

Мое мнение о скорости исключения против проверки данных программно.

У многих классов был конвертер строк в значения (сканер / парсер), уважаемые и известные библиотеки;)

обычно имеет форму

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

имя исключения - только пример, обычно не проверяется (время выполнения), поэтому объявление throws - только моя картинка

иногда существует вторая форма:

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 и т. д., я считаю, что после ваших лекций довольно быстро.

Не бойся исключений


-5

Почему исключения должны быть медленнее, чем обычно?

Пока вы не печатаете трассировку стека в терминал, не сохраняете ее в файл или что-то подобное, блок catch не выполняет больше работы, чем другие кодовые блоки. Итак, я не могу себе представить, почему «throw new my_cool_error ()» должно быть таким медленным.

Хороший вопрос, и я с нетерпением жду дополнительной информации по этой теме!


17
Исключение должно захватывать информацию о трассировке стека, даже если она фактически не используется.
Джон Скит
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.