В этой статье, посвященной 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 секунд, предпочтительно дольше и в идеале в течение нескольких часов.