C#中的try-catch如何提高性能?

3

我的 try catch 知识相对较少。但我想知道它是否可以用于提高性能。

例如,我正在创建一个体素引擎,其中一个函数如下:

Block GetBlockInChunk(Vector Position){
    if(InBound(Position)){
        return Database.GetBlock();
    }
    return null;
}

在这里,需要使用try catch检查给定位置的边界值,然后您可以删除它们吗?

Block GetBlockInChunk(Vector Position){
    try{
        return Database.GetBlock();
    }
    catch{
        return null;
    }
}

我觉得这可能是可怕的做法,但我很好奇。


据我所知,try..catch会引入开销并且不会提高性能。 - Zein Makki
1
它仅适用于异常情况,不应在默认情况下使用。在这种情况下,检查边界似乎比让其引发异常更好。 - Sami Kuhmonen
2
你应该避免使用异常。只有在你没有预料到的情况下才使用异常。try 并不慢,catch 才会慢。 - Jeroen van Langen
我们不应该过于笼统地概括,但是在这种情况下似乎是个坏主意。同样,未经资格的catch绝对是一个非常糟糕的主意。但如果你想知道性能方面的问题,为什么不运行一些快速测试呢?在这种情况下,结果将是显而易见的。 - Jon
@JeroenvanLangen 如果异常情况很少甚至没有,而且大多数情况是由人为错误引起的,那么入站检查只是为了安全起见,这样做是否是一个好主意呢? - filipot
显示剩余5条评论
2个回答

8

我在上面的评论中提供的链接展示了为什么你不应该在if语句可以预防异常时使用try-catch,但是为了展示实际数字方面的性能,我编写了这个快速小测试程序。

Stopwatch watch = new Stopwatch();

int[] testArray = new int[] { 1, 2, 3, 4, 5 };
int? test = null;

watch.Start();
for (int i = 0; i < 10000; i++)
{
    try
    {
        testArray[(int)test] = 0;
    }
    catch { }
}
watch.Stop();

Console.WriteLine("try-catch result:");
Console.WriteLine(watch.Elapsed);
Console.WriteLine();

watch.Restart();
for (int i = 0; i < 10000; i++)
{
    if (test != null)
        testArray[(int)test] = 0;
}
watch.Stop();

Console.WriteLine("if-statement result:");
Console.WriteLine(watch.Elapsed);

这个程序的结果是这样的:
try-catch result:
00:00:32.6764911

if-statement result:
00:00:00.0001047

正如您所看到的,当捕获异常时,使用try-catch方法会引入显着的开销,在我的机器上完成10,000次循环需要超过30秒的时间。而使用if语句,则运行速度非常快,基本上是瞬间完成的。与try-catch相比,这是一种性能提升近300万倍的改进。
(虽然这不是一个严谨的基准测试,并且有不同的编写和运行方式可以获取更精确的数字,但这应该给您一个很好的想法:在可能的情况下,使用if语句而不是try-catch更加高效。)

2
当异常抛出时,“堆栈跟踪”是非常耗时的操作(与简单的“if”相比)。 - Dmitry Bychenko
非常棒的答案!3,000,000%简直是疯了!哈哈 - filipot

0

虽然已经过去了大约两年,但这仍然有关键意义...

我尊重其他回复,但我认为在 try-catch 的目的方面存在一个基本误解。在 C# 和 dotnet 中,Try Catch 是一种非常有效的方式,可以在开发和代码生命周期内识别错误。它永远不是一种用于不同目的的工具,如果被触发,那就意味着您有一个需要修复的错误。

它来解决的问题是标准的错误消息会停止代码,然后你需要挖掘。使用 try 和 catch,你可以知道哪个方法出现了问题,并缩小搜索范围。

通常情况下,我使用 try-catch 包装所有我的方法,并添加了额外功能,将错误消息与方法名称和其他必要信息(如时间以及一些数据锚点)写入调试文件中,即使在生产环境中也可以访问该文件。这是无价之宝!

至于性能,如果 try-catch 没有触发(应该是正常情况),则没有性能降低,因为它只是一个简单的包装器。如果有人真的追求高水平的性能,每一点都很重要,可以使用预编译器条件(#if...)将其消除。

希望这有帮助。


2
抱歉,这并不一定意味着存在“错误”。例如,在写入文件时磁盘空间耗尽时会发生异常。这是“异常情况”,需要处理,但它不是代码中的错误。 - KevinO
这个回答让我想起了20年前我作为初级开发人员在第一份工作中所做的事情。当时我也为每个方法都包装了(VB6)相当于try-catch的东西,原因完全相同——想要调试错误。 我在这个回答中找不到任何真实、有用或良好实践的内容,因此我给它点了个踩。 - bboyle1234
1
@bboyle1234 公平地说,有一些有用的信息,即:“就性能而言,如果try-catch没有触发(这应该是正常情况),则不会降低性能”,这是我花了一段时间才理解的。只有在生成堆栈跟踪时才会出现性能损失。@Shai 不需要对每个方法进行Try ... Catch,因为堆栈跟踪已经包含了您要查找的信息。 - Ama
1
@Ama,我之前使用了 .net benchmark 进行测试,并发现即使没有触发任何异常,性能也确实有所下降。也许我错了——是否有关于此的文档?我喜欢你的公正感 :) - bboyle1234
1
我没有找到任何官方文档,但是这里有一个完整的讨论:https://dev59.com/SnM_5IYBdhLWcg3wmkYK。简而言之,似乎只有JIT优化在某些情况下可能会受到Try Catch块的影响,而这些优化通常很小。 - Ama
可能已经过去了2年,但通知让我回来了。在每个方法调用中都包装try-catch会有性能损失,但如果从未抛出异常,则该惩罚是可以忽略的。真正的问题是可读性和可维护性。到处编写try-catch将会降低生产力,因为每个方法调用都是90%的样板try-catch代码,而任何其他人来阅读代码都会发现一堆与核心逻辑无关的异常处理程序。需要尝试捕获的调用,但是尝试捕获_everything_是代码异味。 - Abion47

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接