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

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

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);
}

Ответ 1

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

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

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

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

Ответ 2

Используя приведенный ниже код, тестирование показало, что вызов + возврат без каких-либо исключений составлял около 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, а не только в режиме Debug. Попробуйте сам код и посмотрите, получаете ли вы значительно разные результаты.

Ответ 3

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

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

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

Бросок исключения - относительно недорогая операция. Это улавливает их, что приводит к расходам из-за стеков, которые должны произойти, чтобы найти обработчики уловов, выполнить код в обработчиках 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;
}

Ответ 10

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

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

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

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

Ответ 11

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

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

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

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