Попробуйте, наконец, дорого

14

В случае кода, в котором вы должны выполнить очистку ресурса перед выходом из функции, существует ли существенная разница в производительности между этими двумя способами.

  1. Очистка ресурса перед каждым оператором возврата

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

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

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

user93353
источник
3
Не стоит публиковать ответ, но нет. Если вы используете Java, который ожидает, что исключения будут стратегией обработки ошибок по умолчанию, тогда вы должны использовать try / catch / finally - язык оптимизирован для используемого вами шаблона.
Джонатан Рич
1
@JonathanRich: как так?
Хайлем
2
Пишите код так, чтобы он выражал то, что вы пытаетесь достичь. Оптимизируйте позже, когда узнаете, что у вас есть проблема - не избегайте языковых возможностей, потому что вы думаете, что можете сэкономить несколько миллисекунд от чего-то, что не является медленным с самого начала.
Шон МакSomething
@mikeTheLiar - Если ты имеешь в виду, что я должен написать if(!cond), то я сделал это с помощью Java. В C ++, это, как я пишу код оба булевых , а также для других типов - то есть int x; if(!x). Поскольку Java позволяет мне использовать это только для booleans, я полностью прекратил использовать if(cond)& if(!cond)в Java.
user93353
1
@ user93353 это меня огорчает. Я бы предпочел увидеть, а (someIntValue != 0)не сравнивать, а не оценивать логические значения. Это пахнет для меня, и я немедленно делаю рефакторинг, когда вижу его в дикой природе.
MikeTheLiar

Ответы:

23

Как указано в разделе Как медленно работают исключения Java? Можно видеть, что медлительность try {} catch {}находится в пределах самого исключения.

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

В примере, приведенном в этом вопросе, нет никаких исключений, не следует ожидать замедления при их создании - они не создаются. Вместо этого то, что здесь, является try {} finally {}обработкой освобождения ресурса в блоке finally.

Таким образом, чтобы ответить на вопрос, нет, в try {} finally {}структуре, которая не использует исключения, нет реальных затрат времени выполнения (это не неслыханно, как видно). Что может быть дорогостоящим, так это время обслуживания, когда кто-то читает код и видит этот нетипичный стиль кода, и кодировщик должен решить, что в этом методе происходит что-то еще после того, returnкак он возвращается к предыдущему вызову.


Как уже упоминалось, техническое обслуживание является аргументом для обоих способов сделать это. Для протокола, после рассмотрения, мои предпочтения будут окончательным подходом.

Подумайте о том, сколько времени нужно обучить новой языковой структуре. Видение try {} finally {}- это не то, что часто можно увидеть в коде Java, и поэтому оно может сбить людей с толку. Существует время, необходимое для изучения немного более сложных структур в Java, чем то, что люди знакомы с просмотром.

finally {}Блок всегда работает. И именно поэтому вы должны его использовать. Также учтите время обслуживания отладки не-окончательного подхода, когда кто-то забывает включить выход в нужный момент, или вызывает его в неподходящее время, или забывает вернуться / выйти после вызова, чтобы он вызывался дважды. При этом существует так много возможных ошибок, которые try {} finally {}невозможно использовать.

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

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

Сообщество
источник
1
В нерегламентированном редактировании говорилось так: «попробуй / наконец-то предлагает гарантии, которых у тебя не было бы иначе в отношении исключений во время выполнения. Нравится ли тебе это или нет, любая строка практически способна вызвать исключение» - Я: это не совсем верно , Структура, приведенная в вопросе, не имеет каких-либо дополнительных исключений, которые могли бы замедлить производительность кода. Никаких дополнительных воздействий на производительность нет, если обернуть блок try / finally по сравнению с блоком без try / finally.
2
Что может быть даже более дорогим в обслуживании, чем понимание этого использования try-finally, так это необходимость поддерживать несколько блоков очистки. Теперь, по крайней мере, вы знаете, что если требуется дополнительная очистка, есть только одно место для ее очистки.
Барт ван Инген Шенау
@BartvanIngenSchenau Действительно. Try-finally - это, вероятно, лучший способ справиться с этим, просто это не так часто встречается в структуре, которую я видел в коде - у людей достаточно проблем, пытаясь понять, попытаться поймать окончательно и обработать исключение в целом ... но попробуйте -финалы без подвоха? Какая? Люди действительно этого не понимают . Как вы предложили, лучше понять структуру языка, чем пытаться ее избежать и делать что-то плохо.
В основном я хотел сделать так, чтобы альтернатива try-finally тоже не была бесплатной. Я хотел бы дать еще один голос за ваше дополнение о расходах.
Барт ван Инген Шенау
5

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

Принятый ответ на этот вопрос показывает, что перенос вызова функции в блок try catch стоит менее 5% по сравнению с простым вызовом функции. Фактически, выброс и перехват исключительной ситуации привел к тому, что время выполнения увеличилось в 66 раз по сравнению с простым вызовом функции. Поэтому, если вы ожидаете, что дизайн исключений будет генерироваться регулярно, тогда я постараюсь избежать его (в правильно профилированном критичном для производительности коде), однако, если исключительная ситуация встречается редко, это не имеет большого значения.

stonemetal
источник
3
«если исключительная ситуация редка» - ну тут есть тавтология. Исключительные означает редкие, и именно так следует использовать исключения. Никогда не должно быть частью проектирования или контроля потока.
Джесси С. Slicer
1
Исключения не обязательно редки на практике, это просто случайный побочный эффект. Например, если у вас плохой пользовательский интерфейс, люди могут вызывать поток
сценариев
2
В коде вопроса нет исключений.
2
@ JesseC.Slicer Есть языки, в которых нередки случаи, когда они используются в качестве управления потоком. В качестве примера, при выполнении итерации чего-либо в python итератор выдает исключение остановки, когда это делается. Кроме того, в OCaml их стандартная библиотека выглядит «очень счастливой», она генерирует большое количество не редких ситуаций (если у вас есть карта и вы пытаетесь найти что-то, чего нет на карте, которую она выбрасывает) ).
каменная металлургия
@stonemetal Как Python диктует, с KeyError
Izkata
4

Вместо оптимизации - подумайте о том, что делает ваш код.

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

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

Если вход в систему и выход из нее на самом деле не являются частью поведения функции, то я бы предложил, чтобы метод хорошо выполнял одну вещь:

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

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

            dootherstuff();

    }

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

Ваш пост помечен как Java, с которым я не очень знаком, но в .net я бы использовал блок using для этого:

то есть:

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

И у контекста входа есть метод Dispose, который будет выходить из системы и очищать ресурсы.

Изменить: я на самом деле не ответил на вопрос!

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

Я собираюсь оставить остальную часть моего ответа, потому что, хотя и не отвечаю на вопрос, я думаю, что это полезно для ОП.

Майкл
источник
1
Чтобы завершить ваш ответ, Java 7 представила оператор try-with-resources, который был бы похож на вашу .net- usingконструкцию, предполагая, что рассматриваемый ресурс реализует AutoCloseableинтерфейс.
Хайлем
Я бы даже удалил эту retпеременную, которая просто назначается дважды для проверки на одну строку позже. Сделай это if (dosomething() == false) return;.
ot--
Согласился, но я хотел сохранить код ОП так, как он был предоставлен.
Майкл
@ ott-- Я предполагаю, что код OP является упрощением их варианта использования - например, у них может быть что-то более похожее ret2 = doSomethingElse(ret1), но это не относится к вопросу, поэтому он был удален.
Изката,
if (dosomething() == false) ...плохой код Используйте более интуитивныйif (!dosomething()) ...
Steffen Heil
1

Предположение: вы разрабатываете на C # код.

Быстрый ответ заключается в том, что при использовании блоков try / finally не наблюдается существенного снижения производительности. Когда вы бросаете и ловите исключения, производительность падает.

Чем дольше ответ, тем лучше для себя. Если вы посмотрите на сгенерированный базовый код CIL ( например, рефлектор red-gate, то вы сможете увидеть сгенерированные базовые инструкции CIL и увидеть влияние таким образом.

Майкл Шоу
источник
11
Вопрос помечен java .
Йоахим Зауэр
Хорошо подмечено! ;)
Майкл Шоу
3
Кроме того, методы не с большой буквы, хотя это может быть просто хороший вкус.
Эрик Реппен
все еще хороший ответ, если вопрос был c # :)
PhillyNJ
-2

Как и другие уже отмечалось, Ttese код будет иметь разные результаты, если либо dosomethingelseили dootherstuffвыбросить исключение.

И да, любой вызов функции может вызвать исключение, по крайней мере, a StackOVerflowError! Несмотря на то, что очень редко можно обрабатывать их правильно (так как любая вызываемая функция очистки, вероятно, будет иметь такую ​​же проблему, в некоторых случаях (например, при реализации блокировок), важно даже правильно обработать это ...

Штеффен Хейл
источник
2
кажется, это не дает ничего существенного по сравнению с предыдущими 6 ответами
комнат