Насколько медленны исключения Java? - PullRequest
447 голосов
/ 18 ноября 2008

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

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

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

и

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

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

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

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

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

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

Ответы [ 17 ]

3 голосов
/ 18 марта 2012

Исключительная производительность в 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 голосов
/ 18 ноября 2008

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

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

2 голосов
/ 04 января 2011

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

1 голос
/ 21 марта 2018

Отличный пост об исключительной ситуации:

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 голосов
/ 18 ноября 2008

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

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

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

0 голосов
/ 12 сентября 2015

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

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

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

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
{
}

без анализа трассировки стека и т. Д., Я полагаю, после ваших лекций довольно быстро.

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

0 голосов
/ 08 ноября 2011

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