Соображения по производительности при выбрасывании исключений

StackOverflow https://stackoverflow.com/questions/6891

Вопрос

Я много раз сталкивался со следующим типом кода, и мне интересно, является ли это хорошей практикой (с точки зрения производительности) или нет:

try
{
    ... // some code
}
catch (Exception ex)
{
    ... // Do something
    throw new CustomException(ex);
}

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

Чем это отличается по производительности от следующих двух:

try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw ex;
}

или

try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw;
}

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

Это было полезно?

Решение

@Брэд Таттероу

Исключение не теряется в первом случае, оно передается конструктору.Однако я соглашусь с вами в остальном, второй подход - очень плохая идея из-за потери трассировки стека.Когда я работал с .NET, я сталкивался со многими случаями, когда другие программисты поступали именно так, и это бесконечно расстраивало меня, когда мне нужно было увидеть истинную причину исключения, только для того, чтобы обнаружить, что оно повторно скопировано из огромного блока try, где я теперь понятия не имею, откуда возникла проблема.

Я также поддерживаю комментарий Брэда о том, что вам не стоит беспокоиться о выступлении.Такого рода микрооптимизация - УЖАСНАЯ идея.Если вы не говорите о создании исключения на каждой итерации цикла for, который выполняется в течение длительного времени, вы, скорее всего, не столкнетесь с проблемами производительности при использовании вашего исключения.

Всегда оптимизируйте производительность, когда у вас есть показатели, указывающие на НЕОБХОДИМОСТЬ оптимизации производительности, а затем обращайте внимание на те места, которые, как доказано, являются виновниками.

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

Последнее замечание о переносе исключений в пользовательское исключение...это может быть очень полезной конструкцией, особенно при работе с пользовательскими интерфейсами.Вы можете обернуть каждый известный и разумный исключительный случай в некоторое базовое пользовательское исключение (или то, которое простирается от указанного базового исключения), и тогда пользовательский интерфейс может просто перехватить это базовое исключение.При перехвате исключение должно будет предоставить средства отображения информации пользователю, скажем, свойство ReadableMessage или что-то в этом роде.Таким образом, каждый раз, когда пользовательский интерфейс пропускает исключение, это происходит из-за ошибки, которую вам нужно исправить, и каждый раз, когда он улавливает исключение, это известное состояние ошибки, которое пользовательский интерфейс может и должен обрабатывать должным образом.

Другие советы

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

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

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

  1. нехорошо, что вы создаете каждое исключение как объект Exception, потому что вы теряете (вероятно) ценную информацию
  2. нехорошо также поднимать все виды объектов, которые вы улавливаете, потому что при этом вы терпите неудачу в создании фасада

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

В качестве дополнительного примечания:

Мне лично не нравится использование исключений (иерархий классов, производных от класса Exception) для реализации логики.Как в том случае:

try {
        // something that will raise an exception almost half the time
} catch( InsufficientFunds e) {
        // Inform the customer is broke
} catch( UnknownAccount e ) {
        // Ask for a new account number
}

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

FxCop всегда рекомендует третий подход, а не второй, чтобы исходная трассировка стека не была потеряна.

Редактировать:Удалили то, что было просто неправильно, и Майк был достаточно любезен, чтобы указать на это.

Не делай этого:

try
{
    // some code
}
catch (Exception ex) { throw ex; }

Так как при этом будет потеряна трассировка стека.

Вместо этого делайте:

try
{
    // some code
}
catch (Exception ex) { throw; }

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

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

Я лично использую пользовательские исключения, если хочу отделить определенные зависимости в коде.Например, у меня есть метод, который загружает данные из XML-файла.Это может пойти не так по-разному.

Может произойти сбой при чтении с диска (FileIOException), пользователь может попытаться получить к нему доступ откуда-нибудь, где он не разрешен (SecurityException), файл может быть поврежден (XmlParseException), данные могут быть в неправильном формате (DeserialisationException).

В этом случае, чтобы вызывающему классу было проще разобраться во всем этом, все эти исключения повторно генерируют одно пользовательское исключение (FileOperationException), так что это означает, что вызывающему классу не нужны ссылки на System.IO или System.Xml, но все равно может получить доступ к тому, какая ошибка произошла, через перечисление и любую важную информацию.

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

public bool Load(string filepath)
{
  if (File.Exists(filepath)) //Avoid throwing by checking state
  {
    //Wrap anyways in case something changes between check and operation
    try { .... }
    catch (IOException ioFault) { .... }
    catch (OtherException otherFault) { .... }
    return true; //Inform caller of success
  }
  else { return false; } //Inform caller of failure due to state
}

Бросок в вашем первом примере связан с накладными расходами на создание нового объекта CustomException.

Повторный запуск во втором примере вызовет исключение типа Exception.

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

Таким образом, второй и третий примеры используют меньше ресурсов.

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

Я видел требования к производительности только в отношении успеха, но никогда в отношении неудачи.

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

Сказав, что эти три блока кода имеют очень различное (внешнее) поведение, поэтому сравнивать их - все равно что спрашивать, является ли быстрая сортировка более эффективной, чем добавление элемента в красно-черное дерево.Это не так важно, как выбрать правильный поступок.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top