在性能方面使用try {} catch {}块的最佳做法是什么?
foreach (var one in all)
{
try
{
//do something
}
catch { }
}
Run Code Online (Sandbox Code Playgroud)
要么
try
{
foreach (var one in all)
{
// do something
}
}
catch { }
Run Code Online (Sandbox Code Playgroud)
没有硬性和快速的规则是公平的,它是情境性的.
这取决于您是否要在其中一个项目导致问题时停止整个循环,或者只是捕获该单个问题并继续.
例如,如果您要向人们发送电子邮件,您不希望在发送其中一个发生异常时停止处理,但如果您正在管理一组数据库事务并且如果其中任何一个失败则需要回滚,可能是更希望在异常/问题上停止处理?
在任何一种情况下性能都可能相同(但如果你想确定的话,可以运行一些测试).每次循环都会发生异常检查,只是在被捕时跳到其他地方.
但是,行为是不同的.
在第一个示例中,将捕获一个项目上的错误,并且循环将继续用于其余项目.
在第二个中,一旦你遇到错误,循环的其余部分将永远不会被执行.
根据要求,这是我很酷的答案。有趣的部分将在结尾,因此,如果您已经知道什么是try-catch,请随时滚动。(对不起,部分题外话)
让我们从回答try-catch的概念开始。
为什么?因为此问题表明缺乏有关如何以及何时使用此功能的完整知识。
(本章也称为:为什么您还没有使用Google来了解它?)
尝试-可能不稳定的代码,这意味着您应该将所有稳定的部分移出。它总是执行,但不保证完成。
捕获-在此处放置旨在纠正在“尝试”部分中发生的故障的代码。仅当Try块中发生异常时才执行。
最后-第三部分和最后一部分,在某些语言中可能不存在。它总是被执行。通常,它用于释放内存和关闭I / O流。
通常,try catch是一种将潜在不稳定代码与程序其余部分分离的方法。在机器语言方面,可以将其缩短为将所有处理器寄存器的值放在堆栈上以免损坏,然后通知环境以忽略执行错误,因为它们将由代码手动处理。
完全不使用它们。用try-catch覆盖代码意味着您期望它会失败。为什么代码会失败?因为它写得不好。无论是从性能还是从质量上来说,编写不需要尝试捕获即可安全工作的代码要好得多。
有时,尤其是在使用第三方代码时,try-catch是最简单,最可靠的选择,但是大多数时候,在自己的代码上使用try-catch表示设计问题。
例子:
数据解析-在数据解析中使用try-catch非常非常糟糕。有很多方法可以安全地解析甚至最奇怪的数据。其中最丑陋的方法之一是正则表达式方法(有问题吗?使用正则表达式,问题很可能是复数)。字符串到整数转换失败?首先检查您的数据,.NET甚至提供了TryParse之类的方法。
除以零,精度问题,数值溢出-不要用try-catch覆盖它,而要升级代码。算术代码应从好的数学方程式开始。当然,您可以对数学方程进行大量修改以使其运行更快(例如,通过 0x5f375a86),但是开始时您仍然需要良好的数学。
列出索引超出范围,堆栈溢出,分段错误,心跳出血-在这里,您在代码设计中会遇到更大的错误。这些错误应该不会在运行良好的环境中正确编写的代码中发生。所有这些都导致一个简单的错误,代码已确保索引(内存地址)在预期的边界内。
I / O错误-尝试使用流(内存,文件,网络)之前,第一步是检查流是否存在(不为null,文件存在,连接打开)。然后检查流是否正确-索引的大小是否合适?可以使用流了吗?它的队列/缓冲区容量是否足以容纳您的数据?所有这些都可以完成,而无需单次尝试捕获。特别是在框架(.NET,Java等)下工作时。
当然,仍然存在意外访问问题的问题-刺伤您的网络电缆,硬盘驱动器熔化。在这里,try-catch的使用不仅可以被原谅,而且应该发生。仍然需要以正确的方式完成操作,例如本示例中的files。您不应将整个流操作代码放在try-catch中,而应使用内置方法来检查其状态。
错误的外部代码-当您使用可怕的代码库而没有任何纠正它的方法时(欢迎使用公司环境),尝试捕获通常是保护其余代码的唯一方法。但是同样,只有直接危险的代码(在编写不良的库中调用可怕的函数)应该放在try-catch中。
可以用一个非常简单的问题来回答。
我可以更正代码以免尝试捕获吗?
是?然后删除该try-catch并修复您的代码。
没有?然后在try-catch中包装不稳定的零件并提供良好的错误处理。
第一步是要知道会发生什么类型的异常。现代环境提供了在类中分离异常的简便方法。可以捕获最具体的异常。做I / O?捕获I / O。在做数学吗?赶上算术的。
什么用户应该知道?
只有用户可以控制:
其他异常只会告知用户您的代码编写有多么糟糕,因此请坚持使用神秘的内部错误。
正如很多人所说的那样,对这个问题没有确切的答案。这完全取决于您提交的代码。
一般规则可能是:原子任务,每个迭代都是独立的-在循环内部进行try-catch。链式计算,每次迭代都取决于先前的迭代-在循环中尝试捕获。
for和foreach有何不同?
Foreach循环不能保证按顺序执行。听起来很奇怪,几乎不会发生,但仍然可能。如果将foreach用于创建任务(数据集操作),则可能需要在其周围放置try-catch。但是如前所述,您应该尽量不要经常使用try-catch。
亲爱的读者,这篇文章的真正原因只是您的几句!
根据Francine DeGrood Taylor的要求,我将在有趣的部分上写一些内容。请记住,正如Joachim Isaksson所注意到的,一见钟情很奇怪。
尽管本部分将重点放在.NET上,但它可以应用于其他JIT编译器,甚至部分地应用于汇编。
那么.. try-catcharound循环如何能够加快速度呢?只是没有任何意义!错误处理意味着额外的计算!
检查有关此Stackoverflow的问题:尝试捕获可以加快我的代码的速度吗? 您可以在那里阅读.NET特定内容,在这里我将尝试着重于如何滥用它。请记住,此问题来自2012年,因此在当前.NET版本中也可以“更正”(这不是错误,它是功能!)。
如上所述,try-catch将代码片段与其余部分分离。分离过程的工作方式与方法类似,因此,除了try-catch之外,您还可以将带有大量计算的循环放入单独的方法中。
分离代码如何加快速度?寄存器。网络比HDD慢,HDD比RAM慢,与超快CPU缓存相比,RAM速度慢。还有一些CPU寄存器,它们嘲笑Cache有多慢。
分离代码通常意味着释放所有通用寄存器-而这正是try-catch所做的。或者说,由于尝试捕获,JIT正在做什么。
JIT最突出的缺陷是缺乏预知能力。它看到循环,然后编译循环。而且,当它最终注意到该循环将执行数千次并且夸大了使CPU吱吱作响的计算时,为时已晚,无法释放寄存器。因此,必须编译循环代码以使用寄存器的剩余内容。
甚至一个额外的寄存器也可以极大地提高性能。每个内存访问时间都非常长,这意味着可以在相当长的时间内不使用CPU。尽管如今我们获得了乱序执行,可爱的流水线和预取功能,但仍有阻塞操作迫使代码停止运行。
现在让我们来谈谈为什么x86与x64相比会很烂。为x64编译时,未发生链接的SE问题中的try-catch速度增益问题,为什么?
因为开始时没有速度增益。所存在的只是由于糟糕的JIT输出导致的速度损失(经典编译器没有此问题)。尝试捕获纠正了JIT行为,主要是偶然的。
x86寄存器是为某些任务创建的。x64体系结构的大小增加了一倍,但是它仍然无法改变以下事实:在执行循环时必须牺牲CX,其他寄存器(可怜的孤儿BX除外)也是如此。
那么,为什么x64如此出色?它拥有8个额外的64位宽寄存器,没有任何特定用途。您可以将它们用于任何用途。从理论上讲,这不仅与x88寄存器一样,而且对任何东西都适用。八个64位寄存器意味着八个64位变量直接存储在CPU寄存器中,而不是直接存储在RAM中,而数学上没有任何问题(这经常需要AX和DX才能得出结果)。64bit还意味着什么?x86可以将Int装入寄存器,x64可以将Long装入。如果数学块有空的寄存器可以工作,则它可以完成大部分工作而不会占用内存。这才是真正的速度提升。
但这还没有结束!您还可以滥用缓存。缓存离CPU越近,它变得越快,但是它也会更小(成本和物理大小是限制)。如果您要优化数据集以一次填充Cache,例如。日期块大小为L1的一半,剩下的一半用于代码以及CPU在缓存中需要的任何内容(除非使用汇编语言,否则您无法真正优化它,必须使用高级语言来“猜测”)。通常,每个(物理)核心都有其自己的L1内存,这意味着您可以一次处理多个缓存的块(但是创建线程并不总是值得的开销)。
值得一提的是,古老的Pascal / Delphi在32位处理器时代使用了“ 16位恐龙”来实现一些重要功能(这使其比C / C ++的32位处理器慢两倍)。因此,请爱护您的CPU寄存器,甚至是可怜的旧BX。他们非常感谢。
再加一点,因为这已经变得很疯狂了,为什么C#/ Java可以同时比本地代码慢和快?JIT是答案,框架代码(IL)被翻译成机器语言,这意味着长的计算块将像C / C ++的本机代码一样执行。但是请记住,您可以轻松地在.NET中使用本机组件(在Java中,尝试这样做可能会发疯)。对于足够复杂的计算,您可以通过本机代码的速度增益来覆盖切换托管本机模式的开销(并且可以通过asm注入来提升本机代码)。