Спробуйте, нарешті, дорого


14

У разі коду, де вам доведеться зробити очищення ресурсу перед виходом із функції, чи є основна різниця в продуктивності між цими двома способами виконання.

  1. Очищення ресурсу перед кожним твердженням про повернення

    void func()
    {
        login();
    
        bool ret = dosomething();
        if(ret == false)
        {
            logout();
            return;
        }
    
        ret = dosomethingelse();
        if(ret == false)
        {
            logout();
            return;
        }
    
        dootherstuff();
    
        logout();
    }
    
  2. Очищення ресурсу, нарешті, блок

    void func()
    {
        login();
    
        try
        {
            bool ret = dosomething();
            if(ret == false)
                return;
    
            ret = dosomethingelse();
            if(ret == false)
                return;
    
            dootherstuff();
    
        }
        finally
        {
            logout();
        }
    }
    

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


3
Насправді не варто публікувати як відповідь, але ні. Якщо ви використовуєте Java, яка очікує, що винятки є стратегією обробки помилок за замовчуванням, тоді вам слід використовувати спробувати / catch / нарешті - мова оптимізована для шаблону, який ви використовуєте.
Джонатан Річ

1
@JonathanRich: як так?
haylem

2
Чисто пишіть код таким чином, щоб він виражав те, що ви намагаєтеся виконати. Оптимізуйте пізніше, коли дізнаєтесь, що у вас є проблеми - не уникайте мовних особливостей, оскільки ви думаєте, що ви можете поголити кілька мілісекунд від чогось, що спочатку не повільно.
Шон Мак-Що-то

@mikeTheLiar - Якщо ти маєш на увазі, що я повинен писати if(!cond), то саме я змусив це зробити. У C ++ саме так я пишу код як для булевих, так і для інших типів - тобто int x; if(!x). Оскільки java дозволяє мені використовувати це лише для booleans, я повністю припинив використовувати if(cond)& if(!cond)in java.
user93353

1
@ user93353, що мене сумує. Я б швидше бачив, (someIntValue != 0)ніж порівнював, ніж оцінював булеві. Мені це пахне, і я його рефакторирую одразу, коли бачу його в дикій природі.
MikeTheLiar

Відповіді:


23

Як зазначено в Наскільки повільними є винятки Java? можна помітити, що повільність try {} catch {}знаходиться в межах встановлення самого винятку.

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

У прикладі, наведеному в цьому запитанні, не існує жодних винятків, і не слід очікувати, що від їх створення виникне уповільнення - вони не створені. Натомість те, що тут, - це try {} finally {}обробка розподілу ресурсів в остаточному блоці.

Отже, щоб відповісти на питання, ні, в try {} finally {}структурі, яка не використовує винятку, немає реальних витрат часу виконання (це не є нечуваним, як видно). Що це може бути дорого час технічного обслуговування , коли один читає код і бачить це не типовий стиль коду та кодер повинен отримати свій розум навколо що - то інше відбувається в цьому методі після того , returnперш ніж повернутися до попереднього виклику.


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

Розглянемо час викладання когось нової мовної структури. Бачити try {} finally {}- це не те, що часто бачать у коді Java, і тому може бентежити людей. Існує певний час обслуговування для вивчення трохи більш досконалих структур на Java, ніж те, що люди знайомі.

finally {}Блок завжди працює. І саме тому вам слід його використовувати. Розглянемо також час налагодження налагодження підходу, що не закінчується, коли хтось забуде включити вихід у належний час, або зателефонує в неналежний час, або забуде повернутися / вийти після виклику, щоб його викликали двічі. З цим існує стільки можливих помилок, що використання try {} finally {}неможливо мати.

Зважуючи ці дві витрати, дорого в час технічного обслуговування не використовувати try {} finally {}підхід. Хоча люди можуть замислюватися про те, скільки дробових мілісекунд чи додаткових інструкцій jvm try {} finally {}блок порівнюється з іншою версією, потрібно також враховувати години, витрачені на налагодження менш ніж ідеального способу вирішення питання про розподіл ресурсів.

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


1
Анонізована редакція сказала таким чином: "спробуйте / нарешті надає гарантії, яких у вас не було б інакше щодо винятків із виконання часу. Хочете ви це чи ні, будь-яка лінія практично здатна викинути виняток" - Мені: це не зовсім так . Структура, наведена у запитанні, не має додаткових винятків, які б уповільнили роботу коду. Немає додаткових впливів на ефективність, обертаючи спробу / нарешті навколо блоку порівняно з блоком без спроби / нарешті.

2
Що може бути навіть дорожчим у технічному обслуговуванні, ніж розуміння цього використання спроб, нарешті, - це підтримка декількох блоків очищення. Тепер ви, принаймні, знаєте, що якщо потрібна додаткова очистка, є лише одне місце.
Барт ван Іґен Шенау

@BartvanIngenSchenau Справді. Спроба - це, мабуть, найкращий спосіб впоратися з цим, це просто не те, що є загальним для структури, яку я бачив у коді - люди мають достатньо труднощів, намагаючись зрозуміти спробу лову нарешті та обробку винятків взагалі .. але спробуйте -фінал без улову? Що? Люди насправді цього не розуміють . Як ви запропонували, краще зрозуміти структуру мови, ніж намагатися її уникати і робити речі погано.

Я здебільшого хотів дати зрозуміти, що альтернатива спробувати, нарешті, також не є безкоштовною. Я хотів би, щоб я міг дати ще одну інформацію про ваші витрати щодо витрат.
Барт ван Інген Шенау

5

/programming/299068/how-slow-are-java-exceptions

Прийнята відповідь на це запитання показує, що завершення функціонування виклику в блок спробу вловлювання коштує менше 5% за голосний виклик функції. Насправді викидання та вилучення винятку спричинило час виконання повітряної кулі більше ніж у 66 разів. Отже, якщо ви очікуєте, що дизайн винятків буде регулярно викидатися, то я б спробував цього уникнути (у правильно профільованому критичному коді щодо продуктивності), однак, якщо виняткова ситуація рідкісна, то це не велика справа.


3
"якщо виняткова ситуація є рідкісною" - ну тут є тавтологія. Винятки означають рідкість, і саме так слід використовувати винятки. Ніколи не слід входити в проектний або контрольний потік.
Jesse C. Slicer

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

2
Не є винятками, які містяться в коді питання.

2
@ JesseC.Slicer Існують мови, де не рідкість їх бачити як контроль потоку. Як приклад, коли ітерація що-небудь у python ітератор кидає виняток зупинки, коли це робиться. Також в OCaml їхня стандартна бібліотека здається дуже "щасливою", вона кидає у великій кількості ситуацій, які не є рідкісними (якщо у вас є карта і намагаєтесь знайти щось, що не існує на карті, яку вона кидає. ).
каменеметал

@stonemetal Як диктує Python, з KeyError
Ізката

4

Замість оптимізації - подумайте, що робить ваш код.

Додавання остаточного блоку - це інша реалізація - це означає, що ви виходите з кожного винятку.

Зокрема - якщо логін кидає виняток, поведінка у вашій другій функції буде відрізнятися від вашої першої.

Якщо вхід та вихід фактично не є частиною поведінки функції, то я б запропонував, що метод повинен добре робити одне і одне:

    void func()
    {
            bool ret = dosomething();
            if(ret == false)
                return;

            ret = dosomethingelse();
            if(ret == false)
                return;

            dootherstuff();

    }

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

Ваша публікація позначена як Java, з якою я не надто знайомий, але в .net, для цього я використовую блок використання:

тобто:

    using(var loginContext = CreateLoginContext()) 
    {
            // Do all your stuff
    }

І в контексті входу є метод Dispose, який здійснюватиме реєстрацію та очищення ресурсів.

Редагувати: Насправді я не відповідав на питання!

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

Я збираюся залишити решту своєї відповіді, тому що, не відповідаючи на запитання, я думаю, що це корисно для ОП.


1
Для завершення вашої відповіді Java 7 представила операцію пробування ресурсів, яка б була схожа на вашу usingконструкцію .net , припускаючи, що відповідний ресурс реалізує AutoCloseableінтерфейс.
haylem

Я б навіть видалив цю retзмінну, яка призначається двічі, щоб перевірити її на один рядок пізніше. Зробіть if (dosomething() == false) return;.
ott--

Погодився, але я хотів зберегти код OP таким, яким він був наданий.
Майкл

@ ott-- Я припускаю, що код ОП - це спрощення випадку їх використання - наприклад, у них може бути щось на кшталт ret2 = doSomethingElse(ret1), але це не стосується питання, тому його було знято.
Ізката

if (dosomething() == false) ...це поганий код. Скористайтеся більш ініціативоюif (!dosomething()) ...
Steffen Heil

1

Припущення: Ви розробляєте код C #.

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

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


11
Питання позначено тегом java .
Йоахім Зауер

Добре помічений! ;)
Майкл Шоу

3
Також методи не використовуються з великої літери, хоча це може бути просто гарним смаком.
Erik Reppen

все ще гарна відповідь, якщо питання було c # :)
PhillyNJ

-2

Як уже зазначали інші, код Ttese матиме різні результати, якщо вони є dosomethingelseабо будуть dootherstuffвинятком.

І так, будь-який виклик функції може кинути виняток, хоча б StackOVerflowError! Незважаючи на те, що рідко можна з цим правильно попрацювати (оскільки будь-яка названа функція очищення, мабуть, матиме ту саму проблему, у деяких випадках (як, наприклад, реалізація замків), важливо навіть правильно попрацювати ...


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