关于性能,使用 try{} catch{} 块的最佳实践是什么?
foreach (var one in all)
{
try
{
//do something
}
catch { }
}
或者
try
{
foreach (var one in all)
{
// do something
}
}
catch { }
关于性能,使用 try{} catch{} 块的最佳实践是什么?
foreach (var one in all)
{
try
{
//do something
}
catch { }
}
try
{
foreach (var one in all)
{
// do something
}
}
catch { }
公平地说,没有硬性规定,这取决于具体情况。
这取决于你是否想在其中一个项目出现问题时停止整个循环,或者只是捕获该单个问题并继续。
例如,如果你正在向人们发送电子邮件,如果发送其中一个电子邮件时发生异常,你不希望停止处理,但如果你正在管理一组数据库事务,并且需要回滚任何失败的事务,也许更希望在异常/问题发生时停止处理吧?
一般来说,try-catch是将潜在不稳定代码与程序的其余部分分离的一种方式。 在机器语言方面,它可以缩短为将所有处理器寄存器的值放置在堆栈上以保存它们免于损坏,然后通知环境忽略执行错误,因为它们将由代码手动处理。
根本不使用它们。用try-catch覆盖代码意味着您期望它失败。为什么代码会失败?因为它编写得很差。 编写不需要try-catch即可安全工作的代码,这对性能和质量都更好。
有时,特别是在使用第三方代码时,try-catch是最简单和最可靠的选择,但大多数情况下,在自己的代码中使用try-catch表示存在设计问题。
例子:
数据解析 - 在数据解析中使用try-catch非常糟糕。有许多安全解析奇怪数据的方法。其中最丑陋的方法之一是正则表达式(遇到问题?使用正则表达式,问题会变得更多)。字符串转换为整数失败?首先检查您的数据,.NET甚至提供了TryParse等方法。
除以零、精度问题、数值溢出 - 不要用try-catch来覆盖它,而是升级您的代码。算术代码应该从好的数学方程开始。当然,您可以大幅修改数学方程以使其运行更快(例如通过0x5f375a86),但您仍然需要良好的数学基础。
列表索引超出范围、堆栈溢出、分段故障、心脏出血 - 这里您的代码设计存在更大的错误。这些错误不应该在健康环境下正常编写的代码中发生。所有这些都归结为一个简单的错误,即代码确保索引(内存地址)在预期边界内。
I/O错误 - 在尝试使用流(内存、文件、网络)之前,第一步是检查流是否存在(非空,文件存在,连接打开)。然后,您检查流是否正确 - 您的索引是否在其大小内?流是否可以使用?它的队列/缓冲器容量是否足够大以容纳您的数据?所有这些都可以在没有单个try catch的情况下完成。特别是当您在框架(.NET、Java等)下工作时。
当然,仍然存在意外访问问题的问题 - 鼠标咬了您的网络电缆,硬盘驱动器融化了。在这里,try-catch的使用不仅可以原谅,而且应该发生。但是,它需要以适当的方式进行,例如文件的此示例。您不应该将整个流操作代码放在try-catch中,而应使用内置方法来检查其状态。
糟糕的外部代码 - 当您开始使用可怕的代码库时,没有任何纠正它的手段(欢迎来到企业世界),try-catch通常是保护其余代码的唯一方法。但是,只有直接危险的代码(调用糟糕编写的库中的可怕函数)应该放在try-catch中。
这可以用一个非常简单的问题来回答。
我能否纠正代码,使其不需要try-catch?
是的?那就放弃try-catch并修复您的代码。
否?然后将不稳定的部分打包在try-catch中,并提供良好的错误处理。
第一步是了解可能发生的异常类型。现代环境提供了一种易于将异常分离为类的方法。尽可能捕获最具体的异常。进行I/O操作?捕获I/O异常。进行数学运算?捕获算术异常。
用户应该知道什么?
只有用户可以控制的内容:
其他异常只会告诉用户您的代码编写得有多糟糕,因此请坚持神秘的“内部错误”。
正如许多人所说,对于这个问题没有定论。这完全取决于您提交的代码。
通用规则可以是: 原子任务,每个迭代都是独立的-在循环内放置try-catch。 链式计算,每个迭代都依赖于前面的迭代-在循环外放置try-catch。请查看这个关于它的Stackoverflow问题:Try-catch speeding up my code?。你可以在那里阅读与.NET相关的内容,这里我将尝试集中讨论如何滥用它。
请注意,这个问题来自2012年,因此它也可能在当前的.NET版本中被“纠正”(它不是一个错误,而是一个特性!)。
如上所述,try-catch将代码片段与其余部分分离开来。分离的过程类似于方法,因此,您也可以将具有重型计算的循环放置在单独的方法中,而不是使用try-catch。
为什么分离代码可以加速它?寄存器。网络比HDD慢,HDD比RAM慢,RAM与超快速的CPU Cache相比也很慢。还有CPU寄存器,它们嘲笑Cache的速度慢。
分离代码通常意味着释放所有通用寄存器-这正是try-catch正在做的事情。或者更确切地说,是JIT由于try-catch而执行的操作。
JIT最显著的缺陷是缺乏预知性。它看到循环,就编译循环。当它最终注意到循环将执行数千次并进行使CPU嘎吱作响的计算时,释放寄存器已经太晚了。因此,循环中的代码必须编译以使用剩余的寄存器。x86寄存器是为了特定任务而创建的。x64架构将其大小加倍,但这仍然无法改变一个事实,即在执行循环时,您必须牺牲CX,其他寄存器也是如此(除了可怜的BX)。
那么为什么x64如此出色呢?它拥有8个额外的64位宽寄存器,没有任何特定用途。您可以将它们用于任何事情。不仅理论上像x88寄存器一样,而且确实可以用于任何事情。八个64位寄存器意味着直接存储在CPU寄存器中的八个64位变量,而不需要为进行数学运算(通常需要AX和DX作为结果)而使用RAM。64位意味着什么?x86可以将Int装入寄存器,而x64可以将Long装入寄存器。如果数学块有空寄存器来工作,它就可以在不触及内存的情况下完成大部分工作。这就是真正的速度提升。
但这并不是结束!您还可以滥用缓存。缓存越接近CPU,速度就越快,但它也会更小(成本和物理大小是限制)。如果您将数据集优化为一次填充缓存,例如以L1的一半大小为单位的日期块,将另一半留给代码和CPU在缓存中发现必要的任何内容(除非您使用汇编语言,否则您无法真正优化它,在高级语言中,您必须“估算”)。通常,每个(物理)核心都有自己的L1内存,这意味着您可以同时处理几个缓存块(但创建线程的开销并不总是值得的)。性能在这两种情况下可能是相同的(但如果您想确保,可以运行一些测试)。异常检查仍然在每次循环时发生,只是在捕获后跳到其他位置。
然而,行为不同。
在第一个例子中,一个项目出现错误会被捕获,循环将继续执行其余项目。
在第二种情况下,一旦出现错误,剩余的循环就永远不会执行。
这取决于你想要实现什么 :) 第一个将在循环中尝试每个函数,即使其中一个失败,其余的也会运行...第二个将在出现错误时中止整个循环...
try...catch
对性能影响较小。实际上可能导致异常的大多数操作,其引起异常所需的时间长得多,因此在大多数情况下,性能差异可以忽略不计。try...catch
,因为在循环内没有任何需要清理的内容。对于大多数应用程序来说,性能差异将是可以忽略的。
但问题是,如果其中一个失败,是否要继续处理循环中的其余项。如果是,请使用内部foreach,否则请使用单个外部循环。
if(){ for(){} }
vsfor(){ if(){} }
:它们有不同的用途。 - clcto