Насколько дороже Исключение, чем возвращаемое значение?

16

Можно ли изменить этот код с возвращаемым значением и исключением:

public Foo Bar(Bar b)
{
   if(b.Success)
   {
      return b;
   }
   else
   {
      throw n.Exception;
   }
}

, что дает отдельные исключения для успеха и неудачи

public Foo Bar(Bar b)
{
   throw b.Success ? new BarException(b) : new FooException();
}

try
{
   Bar(b)
}
catch(BarException bex)
{
   return ex.Bar;
}
catch(FooException fex)
{
   Console.WriteLine(fex.Message);
}
    
задан abatishchev 15.08.2009 в 18:28
источник
  • Это функциональный дубликат stackoverflow.com/questions/99683/... –  dmckee 15.08.2009 в 20:06
  • Независимо от ответа, важно помнить, что стоимость относительно всей программы. Вы можете обнаружить, что 3-строчный фрагмент кода на 1000 раз дороже, когда вы выбрасываете исключение в 100% случаев. Но реальность такова, что исключение может быть выбрано только 1% времени в решении, которое делает такие вещи, как вызовы базы данных или чтение файлов. –  b_levitt 27.10.2017 в 22:11

11 ответов

21

Бросок исключения определенно дороже, чем возврат значения. Но с точки зрения сырьевой стоимости трудно сказать, насколько дороже исключение.

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

  

Используйте исключения только для исключительных обстоятельств

Они никогда не должны использоваться для общего потока управления.     

ответ дан JaredPar 15.08.2009 в 18:30
  • Бывают случаи, когда исключения имеют смысл для общего потока управления - потока управления, для которого язык имеет меньше альтернатив. Например, рассмотрим рекурсивный синтаксический анализатор, который исследует путь разбора спекулятивно - когда он откажется, он захочет вернуться из очень глубокого дерева вызовов, и самый простой способ сделать это без кропотливого проектирования каждой процедуры - бросить исключение. –  Barry Kelly 15.08.2009 в 18:44
  • Я не согласен с «Использовать исключения только для исключительных обстоятельств». Это правило далеко не смутно и субъективно быть эффективным. Выброс «InvalidParameterException» вряд ли является исключительным обстоятельством. –  Alan 15.08.2009 в 18:45
  • @Alan: Передача недопустимого аргумента методу должна быть исключительным обстоятельством. –  Jon Skeet 15.08.2009 в 19:05
  • Я бы сказал, что все, что препятствует нормальной работе программы, является исключительным обстоятельством. –  JulianR 15.08.2009 в 22:05
19

Используя приведенный ниже код, тестирование показало, что вызов + возврат без каких-либо исключений составлял около 1,6 микросекунды на итерацию, тогда как исключения (throw plus catch) добавлялись по микрограмме 4000 каждый. (!)

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        DateTime start = DateTime.Now;
        bool PreCheck = chkPrecheck.Checked;
        bool NoThrow = chkNoThrow.Checked;
        int divisor = (chkZero.Checked ? 0 : 1);
        int Iterations =  Convert.ToInt32(txtIterations.Text);
        int i = 0;
        ExceptionTest x = new ExceptionTest();
        int r = -2;
        int stat = 0;

        for(i=0; i < Iterations; i++)
        {
            try
            {
                r = x.TryDivide(divisor, PreCheck, NoThrow);
            }
            catch
            {
                stat = -3;
            }

        }

        DateTime stop = DateTime.Now;
        TimeSpan elapsed = stop - start;
        txtTime.Text = elapsed.TotalMilliseconds.ToString();

        txtReturn.Text = r.ToString();
        txtStatus.Text = stat.ToString();

    }
}



class ExceptionTest
{
    public int TryDivide(int quotient, bool precheck, bool nothrow)
    {
        if (precheck)
        {
            if (quotient == 0)
            {
                if (nothrow)
                {
                    return -9;
                }
                else
                {
                    throw new DivideByZeroException();
                }

            }
        }
        else
        {
            try
            {
                int a;
                a = 1 / quotient;
                return a;
            }
            catch
            {
                if (nothrow)
                {
                    return -9;
                }
                else
                {
                    throw;
                }
            }
        }
        return -1;
    }
}

Итак, да, Исключения ОЧЕНЬ дорогие.

И прежде чем кто-то скажет это, ДА, я протестировал это в режиме Release , а не только Отладка . Попробуйте сам код и посмотрите, получаете ли вы значительно разные результаты.

    
ответ дан RBarryYoung 15.08.2009 в 21:03
  • +1: для фактического тестирования, а не просто для проверки. Почему программисты спорят о глупом материале, который они могут просто измерить? Меня злит, grrrr ... –  user139593 15.08.2009 в 22:09
  • Спасибо за публикацию этих результатов. Я знал, что это будет большая разница. Но это ДЕЙСТВИТЕЛЬНО большая разница. Хорошая вещь. –  Steve Wortham 16.08.2009 в 01:23
  • Я думаю, что это должен быть принятый ответ, поскольку он дает измеримые рассуждения –  shabby 12.12.2013 в 14:54
  • Я не вижу определения для «txtIterations» в этом коде. Наверное, это текстовое поле, присутствующее в Form1? Какое значение (ы) для txtIterations использовалось, когда оно было запущено? –  Jon Schneider 29.12.2015 в 15:40
  • @JonSchneider да, это был элемент управления textBox. Более шести лет назад, поэтому я не помню наверняка, но я бы предположил, что в диапазоне от 10 000 до 100 000. –  RBarryYoung 30.12.2015 в 00:24
7

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

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

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

    
ответ дан Barry Kelly 15.08.2009 в 18:43
  • Это ловушка, которая берет на себя расходы на сбор стека, поиск обработчиков и т. д., а не бросание. –  Scott Dorman 15.08.2009 в 18:48
  • Сборка стека может быть относительно тривиально сделана довольно дешевой. Обычно только несколько десятков слов нужно копировать из памяти, которая находится в смежном блоке, размером почти не более 1 МБ. Поиск обработчиков также дешев, но вызовы подпрограмм фильтрации могут вызывать множество других кодов, и если логика исключения и диспетчеризации исключений не оптимизирована для частого броска исключений, настройка кадра стека и т. Д. Для каждого шага вся операция может складываться. –  Barry Kelly 15.08.2009 в 18:53
  • @Scott: Я не вижу, чтобы он делал какие-либо выводы, чтобы различать броски и ловли. Они неразрывно связаны - вы не можете иметь одного без другого, поэтому не имеет значения, как вы его называете. –  Jon Skeet 15.08.2009 в 19:05
4

Вы можете найти много полезной информации об этом в ответах на этот вопрос, включая один ответ с 45 up-votes Насколько медленны исключения .net?

    
ответ дан DOK 15.08.2009 в 18:41
  • Что представляется неправильным ... –  user139593 15.08.2009 в 22:11
  • Хммм, какая часть моего ответа неверна, angryboy? URL-адрес, ответы на этот вопрос, тот факт, что у Джона было 45 голосов (сейчас 49) или мое утверждение о наличии «много полезной информации»? –  DOK 15.08.2009 в 22:44
  • Я не сказал, что любая часть вашего ответа была неправильной DOK. Если бы я намеревался это сделать, тогда я бы сказал «это неправильно». Скорее, я отвечал на вашу ссылку: «... включая один ответ с 45 голосами», с: «Который кажется неправильным»., Как и в, содержание ответа, на которое вы ссылаетесь, кажется неправильным , –  RBarryYoung 16.08.2009 в 05:46
  • Спасибо за разъяснение. Было бы очень полезно, если бы вы это усилили в ответ на другой вопрос. Если неправильный ответ Джона неверен, вы будете делать все реальное обслуживание, чтобы объяснить там, как это неверно (помимо вашего краткого комментария там). Это такая важная проблема, и вы, очевидно, имеете с ней глубокий опыт. –  DOK 16.08.2009 в 17:38
2

В настоящее время у меня возникают проблемы с поиском каких-либо документов для его поддержки, но имейте в виду, что когда вы генерируете исключение, C # должен генерировать трассировку стека с той точки, где вы его вызывали. Трассировки стека (и отражение вообще) далеко не свободны.

    
ответ дан Mark Rushakoff 15.08.2009 в 18:35
  • Технически собирать трассировку стека не так дорого. Это, конечно, стоит того, что если данные с высокой стека (стека растут вниз) необходимо вытащить в кеш, чтобы захватить каждый перенаправленный адрес возврата, но это единственная стоимость, которую нужно заплатить, пока кто-то действительно не запросит данные трассировки стека. Вы можете лениво делать отражение и никогда не платить за него, если никто не спрашивает. –  Barry Kelly 15.08.2009 в 18:49
2

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

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

    
ответ дан John Saunders 15.08.2009 в 18:38
1

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

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

    
ответ дан Tom Anderson 15.08.2009 в 18:35
  • Что такое отрицательный голос? –  Tom Anderson 15.08.2009 в 21:42
1

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

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

    
ответ дан Alan 15.08.2009 в 18:37
1

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

Настоятельно рекомендуется не использовать исключения для потока управления. Использование кодов возврата для обозначения ошибок становится дорогостоящим с точки зрения «времени и материалов», поскольку в конечном итоге расходы на обслуживание будут покрыты.

Все это в стороне, ваши два примера не совпадают и даже не верны. Поскольку вы возвращаете b , который имеет тип Bar , ваш первый метод должен быть:

public Bar Foo(Bar b)
{
   if(b.Success)
   {
      return b;
   }
   else
   {
      throw n.Exception;
   }
}

, который можно было бы переписать как:

public Bar Foo(Bar b)
{
   if (!b.Success)
      throw n.Exception;

   return b;
}
    
ответ дан Scott Dorman 15.08.2009 в 18:47
1

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

Однако почему бы просто не вернуть null? Тогда он станет следующим:

public Foo Bar(Bar b)
{
   if(b.Success)
   {
      return b;
   }
   else
   {
      return null;
   }
}

И тогда всякий раз, когда вы вызываете Bar (), вы просто проверяете, чтобы возвращаемое значение не было null, прежде чем использовать значение. Это гораздо менее дорогостоящая операция. И я считаю, что это хорошая практика, потому что это техника, которую Microsoft использовала повсюду во многих встроенных функциях .NET.

    
ответ дан Steve Wortham 15.08.2009 в 18:59
  • Это очень плохая практика, из-за того, что кто-то забудет проверить значение null. Он также помещает код с проверками на то, что должно произойти редко, затеняя то, что на самом деле означает код. –  John Saunders 15.08.2009 в 21:45
  • «Это очень плохая практика, из-за того, что кто-то забудет проверить нуль». - Нельзя ли так же сказать о том, чтобы выбросить исключение? Если вы выбросите исключение, вам нужно запомнить его. –  Steve Wortham 16.08.2009 в 01:14
  • Я полагаю, моя точка зрения заключается в том, что в любом случае это вернется, чтобы укусить вас, если вы не правильно напишите код, который окружает вызов функции. Разница в том, что мой метод намного, намного быстрее, чем бросать и ловить исключение. –  Steve Wortham 16.08.2009 в 01:30
0

Я видел много разных систем, где исключения считались признаком проблемы программного обеспечения, а также означало предоставление информации о событиях, ведущих к ней. Это всегда тяжело в системе. Однако существуют системы, в которых исключение не было исключительным, поскольку сам язык использовал одни и те же или подобные методы для возврата из подпрограммы и т. Д. Таким образом, это сводится к тому, как исключения внедряются в язык и систему. Я сделал измерения в java и в собственной системе, над которой я работаю прямо сейчас, и результаты были как ожидалось - исключительные исключения были (на 20-25%) дороже.

Это не должно быть правило. Интересно, например, как python стоит на этом. Я больше не занимаюсь разработкой python, поэтому я не собираюсь исследовать.

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

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

    
ответ дан umghhh 09.03.2014 в 12:36