Насколько дороги исключения в С#?

Насколько дороги исключения в С#? Похоже, что они не невероятно дороги, если стек не глубокий; однако я прочитал противоречивые сообщения.

Есть ли окончательный отчет, который не был опровергнут?

Ответ 1

Джон Скит написал Исключения и производительность в .NET в январе 2006 года

Что было обновлено Исключения и производительность Redux (спасибо @Gulzar)

На что Рико Мариани присоединился в Истинной стоимости исключений .NET - решение


Также ссылка: Кшиштоф Квалина - Обновление руководств по проектированию: исключение

Ответ 2

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

Для обработки миллиона исключений потребовалось менее 29914 миллисекунд, что составляет 33 исключения за миллисекунду. Это достаточно быстро, чтобы исключения были жизнеспособной альтернативой кодам возврата для большинства ситуаций.

Обратите внимание, что с кодами возврата вместо исключений одна и та же программа работает менее одной миллисекунды, что означает, что исключения по крайней мере в 30 000 раз медленнее, чем коды возврата. Как подчеркивается Rico Mariani, эти числа также являются минимальными числами. На практике бросать и ловить исключение займет больше времени.

Измерено на ноутбуке с Intel Core2 Duo T8100 @2,1 ГГц с .NET 4.0 в версии release не работает под отладчиком (что бы замедлить работу).

Это мой тестовый код:

static void Main(string[] args)
{
    int iterations = 1000000;
    Console.WriteLine("Starting " + iterations.ToString() + " iterations...\n");

    var stopwatch = new Stopwatch();

    // Test exceptions
    stopwatch.Reset();
    stopwatch.Start();
    for (int i = 1; i <= iterations; i++)
    {
        try
        {
            TestExceptions();
        }
        catch (Exception)
        {
            // Do nothing
        }
    }
    stopwatch.Stop();
    Console.WriteLine("Exceptions: " + stopwatch.ElapsedMilliseconds.ToString() + " ms");

    // Test return codes
    stopwatch.Reset();
    stopwatch.Start();
    int retcode;
    for (int i = 1; i <= iterations; i++)
    {
        retcode = TestReturnCodes();
        if (retcode == 1)
        {
            // Do nothing
        }
    }
    stopwatch.Stop();
    Console.WriteLine("Return codes: " + stopwatch.ElapsedMilliseconds.ToString() + " ms");

    Console.WriteLine("\nFinished.");
    Console.ReadKey();
}

static void TestExceptions()
{
    throw new Exception("Failed");
}

static int TestReturnCodes()
{
    return 1;
}

Ответ 3

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

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

Ответ 4

В моем случае исключения были очень дорогими. Я переписал это:

public BlockTemplate this[int x,int y, int z]
{
    get
    {
        try
        {
            return Data.BlockTemplate[World[Center.X + x, Center.Y + y, Center.Z + z]];
        }
        catch(IndexOutOfRangeException e)
        {
            return Data.BlockTemplate[BlockType.Air];
        }
    }
}

В это:

public BlockTemplate this[int x,int y, int z]
{
    get
    {
        int ix = Center.X + x;
        int iy = Center.Y + y;
        int iz = Center.Z + z;
        if (ix < 0 || ix >= World.GetLength(0)
            || iy < 0 || iy >= World.GetLength(1)
            || iz < 0 || iz >= World.GetLength(2)) 
            return Data.BlockTemplate[BlockType.Air];
        return Data.BlockTemplate[World[ix, iy, iz]];
    }
}

И заметил хорошее увеличение скорости около 30 секунд. Эта функция вызывается по меньшей мере 32K раз при запуске. Код не столь ясен, какова намерение, но экономия средств была огромной.

Ответ 5

Объекты исключений Barebones на С# довольно легки; это обычно возможность инкапсулировать InnerException, что делает его тяжелым, когда дерево объектов становится слишком глубоким.

Что касается окончательного отчета, я не знаю никого, хотя беглый профиль dotTrace (или любой другой профайлер) для потребления памяти и скорости будет довольно прост.

Ответ 6

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

     for(; ; ) {
        iValue = Level1(iValue);
        lCounter += 1;
        if(DateTime.Now >= sFinish) break;
     }

против

     for(; ; ) {
        try {
           iValue = Level3Throw(iValue);
        }
        catch(InvalidOperationException) {
           iValue += 3;
        }
        lCounter += 1;
        if(DateTime.Now >= sFinish) break;
     }

Разница в 20 раз. Второй снипп в 20 раз медленнее.

Ответ 7

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

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

Ответ 8

Просто чтобы дать мой личный опыт: Я работаю над программой, которая анализирует файлы JSON и извлекает из них данные с помощью NewtonSoft.

Я переписал это:

  • Вариант 1, с исключениями.
try
{
    name = rawPropWithChildren.Value["title"].ToString();
}
catch(System.NullReferenceException)
{
    name = rawPropWithChildren.Name;
}

На это:

  • Вариант 2, без исключений.
if(rawPropWithChildren.Value["title"] == null)
{
    name = rawPropWithChildren.Name;
}
else
{
    name = rawPropWithChildren.Value["title"].ToString();
}

Конечно, у тебя нет контекста, чтобы судить об этом, но вот мои результаты:
(В режиме отладки)

  • Вариант 1, с исключениями. 38.50s

  • Вариант 2, с исключениями. 06.48s

Чтобы дать немного контекста, я работаю с тысячами свойств json, которые могут быть нулевыми. Исключений было слишком много, например, в течение 15% времени исполнения. Ну, не совсем точно, но их бросали слишком много раз. Я хотел исправить это, поэтому я изменил свой код, и я не понимал, почему время выполнения было намного быстрее. Это было из-за моей плохой обработки исключений.

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

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

Ответ 9

Недавно я измерил исключения С# (throw и catch) в цикле суммирования, который вызывал арифметическое переполнение при каждом добавлении. Бросок и улов арифметического переполнения составляли около 8,5 микросекунд = 117 килограммов исключений/секунду на четырехъядерном ноутбуке.