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

Вопрос: Обработка исключений в 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 не смог правильно оптимизировать код, тем самым замедляя его, Я еще не тестировал эту теорию.

Ответ 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-вызовов
  • Отсутствие оптимизаций наложения и экстремальных вложений против настроек по умолчанию
  • Поля, определяемые пользователем, прочитаны, а не прочитаны

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

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

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

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

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

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

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

  6. Оптимистическое правило большого пальца, похоже, является частотой 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()" должен быть таким медленным.

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