Подтвердить что ты не робот

Насколько дороги Исключения

Знаете ли вы, как дорогое исключение бросания и обработка в java?

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

Сегодня я нашел следующий код в нашем программном обеспечении:

private void doSomething()
{
    try
    {
      doSomethingElse();
    }
    catch(DidNotWorkException e)
    {
       log("A Message");
    }
    goOn();
}
private void doSomethingElse()
{
   if(isSoAndSo())
   {
      throw new DidNotWorkException();
   }
   goOnAgain();
}

Как производительность этого по сравнению с

private void doSomething()
{
    doSomethingElse();
    goOn();
}
private void doSomethingElse()
{
   if(isSoAndSo())
   {
      log("A Message");
      return;
   }
   goOnAgain();
}

Я не хочу обсуждать эстетику кода или что-то еще, это просто поведение во время работы! У вас есть реальный опыт/измерения?

4b9b3361

Ответ 1

Я не удосужился прочитать "Исключения", но очень быстро проверил с каким-то модифицированным вашим кодом. Я пришел к выводу, что ситуация с Exception намного медленнее, чем логический случай.

Я получил следующие результаты:

Exception:20891ms
Boolean:62ms

Из этого кода:

public class Test {
    public static void main(String args[]) {
            Test t = new Test();
            t.testException();
            t.testBoolean();
    }
    public void testException() {
            long start = System.currentTimeMillis();
            for(long i = 0; i <= 10000000L; ++i)
                    doSomethingException();
            System.out.println("Exception:" + (System.currentTimeMillis()-start) + "ms");
    }
    public void testBoolean() {
            long start = System.currentTimeMillis();
            for(long i = 0; i <= 10000000L; ++i)
                    doSomething();
            System.out.println("Boolean:" + (System.currentTimeMillis()-start) + "ms");
    }

    private void doSomethingException() {
        try {
          doSomethingElseException();
        } catch(DidNotWorkException e) {
           //Msg
        }
    }
    private void doSomethingElseException() throws DidNotWorkException {
       if(!isSoAndSo()) {
          throw new DidNotWorkException();
       }
    }
    private void doSomething() {
        if(!doSomethingElse())
            ;//Msg
    }
    private boolean doSomethingElse() {
       if(!isSoAndSo())
          return false;
       return true;
    }
    private boolean isSoAndSo() { return false; }
    public class DidNotWorkException extends Exception {}
}

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

Моя спецификация:

  • Скомпилировано и запущено на 1.5.0_16
  • Sun JVM
  • WinXP SP3
  • Intel Centrino Duo T7200 (2.00Ghz, 977Mhz)
  • 2.00 GB Ram

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

Ответ 2

Исключения не являются бесплатными... так что они дороги: -)

Книга Эффективная Java подробно описывает это.

  • Пункт 39 Используйте исключения только для исключительных условий.
  • Пункт 40 Использовать исключения для восстанавливаемых условий

Автор обнаружил, что исключения привели к тому, что для его тестового примера на его машине в 70 раз медленнее было настроено кодирование кода с его конкретной комбинацией VM и OS.

Ответ 3

Самая медленная часть исключения исключений - заполнение трассировки стека.

Если вы предварительно создали свое исключение и повторно использовали его, JIT может оптимизировать его до " уровень машинного перевода.

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

Ответ 4

Медленная часть об исключениях - это построение трассировки стека (в конструкторе java.lang.Throwable), которая зависит от глубины стека. Бросок сам по себе не замедляется.

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

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

Ниже демонстрируются исключения без следов стека, добавив один простой метод к микротесту (хотя и ошибочный) в ответить:

public class DidNotWorkException extends Exception {
  public Throwable fillInStackTrace() {
      return this;
  }
}

Запуск с использованием JVM в режиме -server (версия 1.6.0_24 в Windows 7) приводит к:

Exception:99ms
Boolean:12ms

Exception:92ms
Boolean:11ms

Разница достаточно мала, чтобы на практике быть невежественной.

Ответ 5

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

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

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

Исключения - очень полезный инструмент, но его следует использовать только при необходимости:)

Ответ 6

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

Хорошо, это ссылка на платформу .NET, но я думаю, что то же самое относится и к Java:

исключения и производительность

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

Ответ 7

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

Ответ 8

Спасибо за все ответы.

Я, наконец, последовал за предложением Thorbjørn и написал небольшую тестовую программу, измеряя производительность сам. Результат: нет разницы между двумя вариантами (в вопросах производительности).

Даже если я не спрашивал об эстетике кода или что-то в этом роде, то есть о намерениях исключений и т.д. большинство из вас также рассматривали эту тему. Но на самом деле вещи не всегда так ясны... В рассматриваемом случае код родился давно, когда ситуация, при которой исключение выбрасывается, кажется исключительной. Сегодня библиотека используется по-разному, поведение и использование разных приложений изменено, покрытие тестов не очень хорошо, но код все еще делает это работу, немного медленнее (вот почему я попросил производительность!). В этой ситуации, я думаю, должна быть веская причина для перехода от А к Б, что, на мой взгляд, не может быть "Это не то, для чего были сделаны исключения!".

Оказалось, что запись ( "Сообщение" ) (по сравнению со всем, что происходит) очень дорого, поэтому я думаю, что я избавлюсь от этого.

ИЗМЕНИТЬ

Тестовый код в точности соответствует таковому в исходном сообщении, вызванном методом testPerfomance() в цикле, который окружен System.currentTimeMillis() -calls, чтобы получить время выполнения... но:

Теперь я просмотрел тестовый код, перевернул все остальное (оператор журнала) и зациклил в 100 раз больше, чем раньше, и оказывается, что вы сохраняете 4,7 секунды для миллиона вызовов при использовании B вместо A от оригинала после. Как сказал Рон, fillStackTrace - самая дорогая часть (+1 для этого), и вы можете сэкономить почти то же самое (4,5 секунды), если вы его перезапишете (в случае, если вам это не понадобится, как и я). В целом это все-таки почти нулевая разница в моем случае, так как код называется 1000 раз в час, и измерения показывают, что я могу сохранить 4,5 миллиса за это время...

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

Ответ 9

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

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

И наоборот, если исключение, как представляется, "используется для правильной вещи", то это, вероятно, означает, что он также выполнит команду "ОК".

Ответ 10

Скажем, исключение не произойдет при попытке выполнить инструкции 1 и 2. Есть ли какие-либо ошибки производительности между этими двумя примерами кода?

Если нет, что делать, если метод DoSomething() должен выполнять huuuge объем работы (множество вызовов другим методам и т.д.)?

1

try
{
   DoSomething();
}
catch (...)
{
   ...
}

2:

DoSomething();