Які ефекти виключень на продуктивність на 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. Деякі люди захищають готи, деякі захищають те, про що ви говорите, але якщо ви запитаєте когось, хто впроваджував і підтримував або протягом певного періоду часу, вони скажуть вам, що обом важко підтримувати дизайнерські практики (і, ймовірно, буде проклинати ім’я людини, яка вважала їх досить розумними, щоб прийняти рішення використовувати їх).
Білл К

80
Білл, стверджуючи, що використання винятків для програмного потоку не є кращим, ніж використання GOTO, не краще, ніж твердження, що використання умовних умов і циклів для потоку програми не є кращим, ніж використання GOTO. Це червона оселедець. Поясніть собі. Винятки можуть і ефективно використовуються для потоку програми іншими мовами. Наприклад, ідіоматичний код Python регулярно використовує винятки. Я можу і підтримую код, який використовує винятки таким чином (але не Java), і я не думаю, що в ньому є щось не так.
mmalone

14
@mmalone, що використовує Винятки для нормального потоку управління, є поганою ідеєю в Java, оскільки вибір парадигми був зроблений саме таким чином . Прочитайте Bloch 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 Якщо якась рамка робить це (використовуйте Винятки в невиключному стані), це хибно і порушено дизайн, порушуючи контракт класу винятків мови. Тільки тому, що деякі люди пишуть паршивий код, це не робить його менш паршивим.

8
Ніхто інший, як творець stackoverflow.com не помиляється про винятки. Золоте правило розробки програмного забезпечення ніколи не робить простий складним і непростим. Він пише: "Це правда, що те, що повинно бути простою 3-рядковою програмою, часто зацвітає до 48 рядків, коли ви ставите хорошу перевірку помилок, але це життя, ..." Це пошук чистоти, а не простоти.
sf_jeff

Відповіді:


345

Це залежить від того, як реалізуються винятки. Найпростіший спосіб - використання setjmp та longjmp. Це означає, що всі регістри процесора записуються в стек (що вже займає деякий час), і, можливо, потрібно створити якісь інші дані ... все це вже відбувається в операторі спробу. Оператор кидка повинен розмотати стек і відновити значення всіх регістрів (та можливих інших значень у VM). Тому спробуйте і кинути однаково повільно, і це досить повільно, однак, якщо не виключається жоден виняток, вихід із блоку спробу в більшості випадків не займає часу (оскільки все ставиться на стек, який автоматично очищається, якщо метод існує).

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

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

Це також має велику різницю в тому, що ви робите в пробному блоці. Якщо ви відкриєте пробний блок і ніколи не викликаєте жодного методу з цього блоку спробу, блок спробу буде надто швидким, оскільки JIT може насправді трактувати кидок, як просту готу. Він також не повинен зберігати стан стека, а також не потрібно розкручувати стек, якщо викинутий виняток (потрібно лише перейти до оброблювачів улов). Однак зазвичай це не те, що ти робиш. Зазвичай ви відкриваєте блок спробу, а потім викликаєте метод, який може кинути виняток, правда? І навіть якщо ви просто використовуєте блок спробу у своєму методі, який це буде метод, який не вимагає жодного іншого методу? Чи просто обчислить число? Тоді для чого потрібні винятки? Є набагато більш елегантні способи регулювання потоку програми. Для майже нічого іншого, крім простої математики,

Дивіться наступний тестовий код:

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

Уповільнення блоку спробу занадто мало, щоб виключати заплутані фактори, такі як фонові процеси. Але блок улову вбив усе і зробив це в 66 разів повільніше!

Як я вже говорив, результат буде не таким поганим, якщо ви поставите спробу / ловити та кинути все в рамках одного методу (метод 3), але це спеціальна оптимізація JIT, на яку я б не покладався. І навіть при використанні цієї оптимізації кидок все ще досить повільний. Тому я не знаю, що ви намагаєтеся зробити тут, але, безумовно, є кращий спосіб зробити це, ніж використовувати спробувати / ловити / кинути.


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

10
@ SimonAndréForsberg nanoTime()вимагає Java 1.5, і у мене була тільки Java 1.4 в системі, яку я використовував для написання коду вище. Крім того, це не грає величезної ролі на практиці. Єдина відмінність між ними полягає в тому, що одна наносекунда, а інша - мілісекунди, і на nanoTimeних не впливають годинникові маніпуляції (які не мають значення, якщо ви або системний процес не змінили системний годинник точно в момент запуску тестового коду). Як правило, ви праві, проте, nanoTimeзвичайно, кращий вибір.
Мецькі

2
Справді слід зазначити, що ваш тест є крайнім випадком. Ви показуєте дуже невеликий показник ефективності для коду з tryблоком, але ні throw. Ваш throwтест кидає винятки 50% часу, який він проходить через try. Очевидно, що ситуація не є винятковою . Зменшивши це лише до 10%, масово скорочується показник ефективності. Проблема такого випробування полягає в тому, що він спонукає людей взагалі перестати використовувати винятки. Використання винятків для виняткових ситуацій у справах працює набагато краще, ніж показано у вашому тесті.
Нейт

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

4
@Glide Кидок не такий, як чистий return. Він залишає метод десь посередині тіла, можливо, навіть в середині операції (що до цього часу завершилося лише на 50%), і catchблок може бути на 20 кадрів стека вгору (метод має tryблок, виклику method1, який викликає method2, який викликає mehtod3, ..., а в методі20 в середині операції викидається виняток). Стек повинен бути розкручений на 20 кадрів вгору, усі незавершені операції повинні бути скасовані (операції не повинні бути наполовину виконані), а регістри процесора повинні бути в чистому стані. Це все вимагає часу.
Mecki

255

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 такі самі, як у Мецького (мій ноутбук явно повільніше).

method4 ідентичний методу3, за винятком того, що він створює, new Integer(1)а не робить throw new Exception().

method5 - це як method3, за винятком того, що він створює new Exception()без викидання.

method6 - це як method3, за винятком того, що він викидає попередньо створений виняток (змінну екземпляра), а не створює новий.

У Java велика частина витрат на викид винятку - це час, витрачений на збір сліду стека, який виникає при створенні об'єкта виключення. Фактична вартість кидання винятку, хоча і велика, значно менша, ніж вартість створення винятку.


48
+1 Ваша відповідь стосується основної проблеми - часу, необхідного для розкручування та відстеження стека, і, вдруге, помилки. Я б вибрав це як остаточну відповідь.
Інженер

8
приємно. ~ 70%, створюючи виняток, ~ 30% кидаючи його. хороша інформація.
чакка

1
@Basil - Ви повинні мати можливість це зрозуміти із наведених вище чисел.
Гарячі лизання

1
Це може бути специфічно для впровадження. Яка версія Java була використана для цих орієнтирів?
Thorbjørn Ravn Andersen

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

70

Олексій Шипілєв зробив дуже ретельний аналіз, в якому порівняв винятки Java за різних комбінацій умов:

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

Він також порівнює їх з ефективністю перевірки коду помилки на різних рівнях частоти помилок.

Висновки (цитовані дослівно з його посади) були:

  1. По-справжньому виняткові винятки чудово виконуються. Якщо ви використовуєте їх як розроблені та повідомляєте лише справді виняткові випадки серед надзвичайно великої кількості невиключних випадків, які обробляються звичайним кодом, то використання винятків - це виграш від продуктивності.

  2. Виконання витрат на винятки мають дві основні складові: побудова трасування стека, коли виняток є екземпляром, і розмотування стека під час скидання винятку .

  3. Витрати на будівництво слідів штабелю пропорційні глибині штабелю в момент опитування виключень. Це вже погано, бо хто на Землі знає глибину штабелю, при якій би називався цей метод метання? Навіть якщо вимкнути генерацію слідів стека та / або кеш винятків, ви можете позбутися лише цієї частини вартості продуктивності.

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

  5. Якщо ми усунемо обидва наслідки, виняток за виняток - це вартість місцевої гілки. Як би красиво це не звучало, це не означає, що ви повинні використовувати Винятки як звичайний керуючий потік, адже в такому випадку вам належить оптимізація компілятора! Використовувати їх слід лише у справді виняткових випадках, коли частота виключень амортизує можливу невдалу вартість підвищення фактичного винятку.

  6. Оптимістична величина правил, як здається, частота 10 ^ -4, за винятками є достатньо винятковою. Це, звичайно, залежить від великої ваги самих винятків, точних дій, що вживаються в обробниках винятків тощо.

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


41

Моя відповідь, на жаль, занадто довга, щоб розміщувати тут. Тож дозвольте мені підвести підсумки тут і направити вас на http://www.fuwjax.com/how-slow-are-java-exceptions/, щоб отримати деталі.

Справжнє питання тут не в тому, "як повільно" помилки повідомляються як винятки "порівняно з" кодом, який ніколи не виходить з ладу "?" як ви можете повірити у прийняту відповідь. Натомість питання повинно бути таким: "Наскільки повільними є" помилки, які повідомляються як винятки "порівняно з помилками, повідомлені іншими способами?" Як правило, два інші способи подання звітів про помилки - або зі значеннями дозорних, або з обертовими результатами.

Значення Sentinel - це спроба повернути один клас у разі успіху, а інший у разі відмови. Ви можете думати про це майже як про повернення винятку, а не про викидання. Для цього потрібен спільний батьківський клас з об'єктом успіху, а потім робиться перевірка "instanceof", і пара робить касти для отримання інформації про успіх чи невдачу.

Виявляється, що загрожуючи безпеці типу, значення Sentinel швидше, ніж винятки, але лише на коефіцієнт приблизно в 2 рази. Зараз це може здатися чималим, але це 2x покриває лише вартість різниці в реалізації. На практиці коефіцієнт набагато нижчий, оскільки наші методи, які можуть виявитися невдалими, набагато цікавіші, ніж кілька арифметичних операторів, як у зразковому коді в інших місцях цієї сторінки.

З іншого боку, обгортки для результатів взагалі не жертвують безпекою типу. Вони містять інформацію про успіх та невдачі в одному класі. Таким чином, замість "instanceof" вони надають "isSuccess ()" і отримують як об'єкти успіху, так і невдачі. Однак об'єкти результатів приблизно в 2 рази повільніше, ніж використання виключень. Виявляється, щоразу створювати новий об’єкт обгортки набагато дорожче, ніж іноді кидати виняток.

Крім цього, винятком є ​​мова, що надається способом вказівки на те, що метод може не працювати. Немає іншого способу сказати лише з API, які методи, як очікується, завжди (в основному) працюють, і які, як очікується, повідомлять про помилки.

Винятки безпечніші за дозорні, швидші за результати, що випливають, і менш дивні, ніж будь-які. Я не пропоную спробувати / catch замінити if / else, але винятки - це правильний спосіб повідомити про невдачу навіть у бізнес-логіці.

Враховуючи це, я хотів би зазначити, що два найчастіші способи істотного впливу на продуктивність, які я зіткнувся, - це створення непотрібних об'єктів і вкладених циклів. Якщо у вас є вибір між створенням виключення чи створенням виключення, не створюйте виняток. Якщо у вас є вибір між створенням винятку іноді або створенням іншого об'єкта весь час, тоді створіть виняток.


5
Я вирішив перевірити довгострокову ефективність трьох реалізацій порівняно з контрольною реалізацією, яка перевіряє наявність несправності без звітності. У процесі відмови близько 4%. Ітерація тесту викликає процес 10000 разів проти однієї із стратегій. Кожна стратегія тестується 1000 разів, а останні 900 разів використовуються для отримання статистики. Ось середній час у нано: Контроль 338 Виняток 429 Результат 348 Sentinel 345
Fuwjax

2
Тільки для розваги я відключив fillInStackTrace у випробуванні винятків. Ось часи зараз: Контроль 347 Виняток 351 Результат 364 Sentinel 355
Fuwjax

Fuwjax, якщо я чогось не пропускаю (і я визнаю, що я читав лише твій пост, а не твій пост у блозі), схоже, два ваші коментарі вище суперечать вашому посту. Я припускаю, що нижчі цифри краще у вашому орієнтирі, правда? У такому разі створення виключень із увімкненою функцією 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

Отже, методи, які повертають значення, швидше, порівняно з методами, що викидають винятки. IMHO, ми не можемо розробити чіткий 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 writableStackTracearg. Але цього немає в Java 6 і нижче. Ось чому я дав власну реалізацію для Java 6 і нижче. Отже, наведений вище код призначений для Java 6 та нижче. Будь ласка, уважно прочитайте перший рядок 2-го параграфа.
manikanta

@manikanta "IMHO, ми не можемо розробити чіткий API, просто використовуючи типи повернення для обох потоків успіху та помилок". Ми можемо, якщо ми використовуємо необов'язкові / результати / можливо, як і багато мов.
Hejazzman

@Hejazzman Я згоден. Але Optionalчи подібне прийшло трохи пізно до Яви. До цього ми також використовували об’єкти обгортки з прапорами успіху / помилки. Але це, здається, трохи хакує і мені не здається природним.
маніканта

8

Нещодавно я написав клас для перевірки відносної продуктивності перетворення рядків у ints, використовуючи два підходи: (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, тому я дуже сумніваюся, що використання винятків швидше, ніж "повернення" в реальному житті. Наприклад, я підозрюю, що у вашому тесті є десь між 5 та 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полягає в тому, що вони кидають різного роду винятки. Yeap, method4ще повільніше , ніж method1та method2, але різниця є набагато більш прийнятним.


6

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

Я бачив подібні цифри у виробничому коді, а також мікро-орієнтири.

Винятки, безумовно, НЕ повинні використовуватися для того, що називається часто. Якщо кинути тисячі винятків на секунду, це призведе до величезної шийки пляшки.

Наприклад, використовуючи "Integer.ParseInt (...)", щоб знайти всі погані значення у дуже великому текстовому файлі - дуже погана ідея. (Я бачив, як цей корисний метод знищує продуктивність у виробничому коді)

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

Незалежно від того, чи є це гарною дизайнерською практикою чи ні, я б погодився з правилом: якщо помилка є нормальною / очікуваною, використовуйте повернене значення. Якщо це ненормально, використовуйте виняток. Наприклад: читання введень користувача, погані значення є нормальними - використовуйте код помилки. Передаючи значення внутрішній функції утиліти, погані значення слід відфільтрувати за допомогою виклику коду - використовуйте виняток.


Дозвольте запропонувати декілька речей, які БУДУТЬ робити: Якщо вам потрібне число у формі, замість того, щоб використовувати Integer.valueOf (String), вам слід замість цього використовувати регулярний відповідник виразів. Ви можете заздалегідь скласти та повторно скористатись візерунком, щоб зробити відповідні кошти дешевими. Однак у формі GUI, маючи isValid / validate / checkField або те, що у вас є, напевно, зрозуміліше. Також у Java 8 у нас є необов'язкові монади, тому подумайте про їх використання. (відповідь 9 років, але все-таки!: p)
Haakon Løtveit

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

Перевірка та поширення повернених значень додає деяку вартість порівняно з базовим нульовим викликом, і ці витрати пропорційні глибині виклику. При глибині ланцюга виклику 8 версія для перевірки помилки повернення і значення була приблизно на 27% повільніше, ніж базова версія, яка не перевіряла значення повернення.

Ефективність виключень, порівняно, не є функцією глибини виклику, а частотою виключення. Однак деградація із збільшенням частоти виключень набагато драматичніша. З 25% -ною частотою помилок, код працював на 24 тайми повільніше. При частоті помилок у 100% версія винятку майже на 100-ЧАС менша.

Це підказує мені, що, можливо, ми робимо неправильні компроміси у наших реалізаціях винятків. Винятки можуть бути швидшими, або уникнувши дорогих прогулянок, або перетворивши їх на перевірку зворотних значень, підтримуваних компілятором. Поки вони не затримуються, уникаючи їх, коли хочемо, щоб наш код швидко працював.


3

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


2

Навіть якщо викид винятків не є повільним, все-таки погана ідея викидати винятки для нормального потоку програми. Використовується таким чином, це аналог GOTO ...

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


1
Було б добре визначити "нормальний потік програми". Багато було написано про використання перевірених винятків як провал бізнес-процесів та неперевірений виняток для невідшкодуваних збоїв, тому в певному сенсі провал у бізнес-логіці все ще можна вважати звичайним потоком.
Спенсер Кормос

2
@Spencer K: Виняток, як випливає з назви, означає, що була виявлена ​​виняткова ситуація (файл відійшов, мережа раптово закрилася, ...). Це означає, що ситуація була НЕЧАСНА. Якщо очікується, що ситуація станеться, я б не використовував для неї виняток.
Mecki

2
@Mecki: правильно. Нещодавно я з кимось обговорював з цього приводу ... Вони писали рамку перевірки і кидали виняток у разі невдачі перевірки. Я думаю, що це погана ідея, оскільки це було б досить часто. Я вважаю за краще, щоб метод повернув ValidationResult.
user38051

2
З точки зору потоку управління, виняток є аналогом a breakабо return, не a 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/exceptions-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 , залежно від того, як часто ви їх кидаєте))


0

Я змінив відповідь @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

0

Виняток призначене для обробки несподіваних умов під час виконання тільки.

Використання винятку замість простої перевірки, яке можна зробити за час компіляції, затримує перевірку до часу виконання. Це в свою чергу знизить ефективність програми.

Якщо викинути виняток замість простої валідації if..else, це також зробить код складним для запису та підтримки.


-3

Моя думка про швидкість винятку проти перевірки даних програмно.

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

зазвичай має форму

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

Ім'я винятку - лише приклад, зазвичай це не перевірено (час виконання), тому декларація про викиди - це лише моя картина

іноді існує друга форма:

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

не аналізуючи стек-трек тощо, я вважаю, що після ваших лекцій досить швидко.

Не бійтеся винятків


-5

Чому винятки повинні бути повільнішими, ніж нормальна віддача?

Поки ви не друкуєте стек-трек до терміналу, зберігаєте його у файл чи щось подібне, блок-фіксатор не виконує більше роботи, ніж інші блоки коду. Отже, я не можу уявити, чому "кидати новий my_cool_error ()" слід так повільно.

Добре запитання, і я з нетерпінням чекаю на подальшу інформацію по цій темі!


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