Попробуйте поймать производительность

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

 private void TryCatchPerformance()
        {
            int iterations = 100000000;

            Stopwatch stopwatch = Stopwatch.StartNew();
            int c = 0;
            for (int i = 0; i < iterations; i++)
            {
                try
                {
                   // c += i * (2 * (int)Math.Floor((double)i));
                    c += i * 2;
                }
                catch (Exception ex)
                {
                    throw;
                }
            }
            stopwatch.Stop();
            WriteLog(String.Format("With try catch: {0}", stopwatch.ElapsedMilliseconds));

            Stopwatch stopwatch2 = Stopwatch.StartNew();
            int c2 = 0;
            for (int i = 0; i < iterations; i++)
            {
              //  c2 += i * (2 * (int)Math.Floor((double)i));
                c2 += i * 2;
            }
            stopwatch2.Stop();
            WriteLog(String.Format("Without try catch: {0}", stopwatch2.ElapsedMilliseconds));
        }

Выход, который я получаю:

With try catch: 68
Without try catch: 34

Итак, похоже, что использование блока try-catch кажется скорее быстрее?

То, что я нахожу еще более странным, это то, что когда я заменяю вычисление в теле for-loops чем-то более сложным, например: c += i * (2 * (int)Math.Floor((double)i));
Разница гораздо менее драматична.

With try catch: 640
Without try catch: 655

Я делаю что-то неправильно здесь или есть логическое объяснение для этого?

Ответы

Ответ 1

JIT не выполняет оптимизацию на блоках "protected" / "try", и я предполагаю, что в зависимости от кода, который вы пишете в блоках try/catch, это повлияет на вашу производительность.

Ответ 2

Сам блок try/catch/finally/fault фактически не имеет накладных расходов в оптимизированной сборке релизов. Хотя часто добавляется дополнительный IL для catch и, наконец, блоков, когда исключение не выбрасывается, мало различий в поведении. Вместо простого ret, обычно есть разрешение на более поздний ret.

Истинная стоимость блоков try/catch/finally возникает при обработке исключения. В таких случаях должно быть создано исключение, должны быть помещены метки обхода стека, и, если обращение к исключению и доступное к нему свойство StackTrace, происходит стекирование стека. Самой тяжелой операцией является трассировка стека, которая следует за ранее установленными метками обхода стека, чтобы создать объект StackTrace, который может использоваться для отображения местоположения, в котором произошла ошибка, и вызовов, через которые он проходил.

Если в блоке try/catch нет поведения, тогда будет преобладать дополнительная стоимость "оставить на ret" или просто "ret", и, очевидно, будет заметная разница. Однако в любой другой ситуации, когда в предложении try есть какое-то поведение, стоимость самого блока будет полностью отменена.

Ответ 3

Обратите внимание, что у меня есть только Моно:

// a.cs
public class x {
    static void Main() {
        int x = 0;
        x += 5;
        return ;
    }
}


// b.cs
public class x {
    static void Main() {
        int x = 0;
        try {
            x += 5;
        } catch (System.Exception) {
            throw;
        }
        return ;
    }
}

Разборка этих:

// a.cs
       default void Main ()  cil managed
{
    // Method begins at RVA 0x20f4
    .entrypoint
    // Code size 7 (0x7)
    .maxstack 3
    .locals init (
            int32   V_0)
    IL_0000:  ldc.i4.0
    IL_0001:  stloc.0
    IL_0002:  ldloc.0
    IL_0003:  ldc.i4.5
    IL_0004:  add
    IL_0005:  stloc.0
    IL_0006:  ret
} // end of method x::Main

и

// b.cs
      default void Main ()  cil managed
{
    // Method begins at RVA 0x20f4
    .entrypoint
    // Code size 20 (0x14)
    .maxstack 3
    .locals init (
            int32   V_0)
    IL_0000:  ldc.i4.0
    IL_0001:  stloc.0
    .try { // 0
      IL_0002:  ldloc.0
      IL_0003:  ldc.i4.5
      IL_0004:  add
      IL_0005:  stloc.0
      IL_0006:  leave IL_0013

    } // end .try 0
    catch class [mscorlib]System.Exception { // 0
      IL_000b:  pop
      IL_000c:  rethrow
      IL_000e:  leave IL_0013

    } // end handler 0
    IL_0013:  ret
} // end of method x::Main

Основное различие, которое я вижу, - a.cs, ​​идет прямо к ret в IL_0006, тогда как b.cs должно leave IL_0013 в IL_006. Мое лучшее предположение с моим примером состоит в том, что leave является (относительно) дорогим прыжком при компиляции в машинный код - это может быть или не быть, особенно в вашем цикле for. То есть, у try-catch нет неотъемлемых накладных расходов, но перепрыгивание через улов имеет стоимость, как любая условная ветвь.

Ответ 4

фактическое вычисление настолько минимально, что точные измерения очень сложны. Мне кажется, что попытка catch может добавить очень небольшое фиксированное количество дополнительного времени в рутину. Мне было бы сложно догадаться, не зная ничего о том, как исключения реализованы на С#, что это в основном просто инициализация путей исключений и, возможно, небольшая нагрузка на JIT.

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

Ответ 5

Проблема первая в вашем тестовом коде. вы использовали секундомер .Elapsed.Milliseconds, который показывает только миллисекунду часть времени Истекшее время, используйте TotalMilliseconds, чтобы получить всю часть...

Если исключение не выбрасывается, разница минимальна

Но реальный вопрос: "Нужно ли проверять исключения или позволить С# обрабатывать бросок исключения?"

Ясно... ручка одна... Попробуйте запустить это:

private void TryCatchPerformance()
    {
        int iterations = 10000;
        textBox1.Text = "";
        Stopwatch stopwatch = Stopwatch.StartNew();
        int c = 0;
        for (int i = 0; i < iterations; i++)
        {
            try
            {   
                c += i / (i % 50);
            }
            catch (Exception)
            {

            }
        }
        stopwatch.Stop();
        Debug.WriteLine(String.Format("With try catch: {0}", stopwatch.Elapsed.TotalSeconds));

        Stopwatch stopwatch2 = Stopwatch.StartNew();
        int c2 = 0;
        for (int i = 0; i < iterations; i++)
        {
            int iMod50 = (i%50);
            if(iMod50 > 0)
                c2 += i / iMod50;
        }
        stopwatch2.Stop();
        Debug.WriteLine( String.Format("Without try catch: {0}", stopwatch2.Elapsed.TotalSeconds));

    }

Вывод:    ОБОРОТ: посмотрите ниже! С try catch: 1.9938401

Без try catch: 8.92E-05

Удивительные, только 10000 объектов, с 200 исключениями.

КОРРЕКЦИЯ: Я запустил свой код в окне DEBUG и VS Written Exception to Output.. Это результаты RELEASE Гораздо меньше накладных расходов, но все же улучшение на 7500%.

С попыткой catch: 0.0546915

Проверка одного: 0.0007294

С try catch Выбрасывание моего собственного объекта Exception: 0.0265229

Ответ 6

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

Ответ 7

Разница всего в 34 миллисекунды меньше, чем погрешность ошибки для теста, подобного этому.

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

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