在 foreach 循环中使用 try-catch 块的最佳做法是啥? [关闭]
Posted
技术标签:
【中文标题】在 foreach 循环中使用 try-catch 块的最佳做法是啥? [关闭]【英文标题】:Which is the best practice to use try - catch blocks with foreach loop? [closed]在 foreach 循环中使用 try-catch 块的最佳做法是什么? [关闭] 【发布时间】:2014-08-17 14:30:00 【问题描述】:在性能方面使用 try catch 块的最佳做法是什么?
foreach (var one in all)
try
//do something
catch
或者
try
foreach (var one in all)
// do something
catch
【问题讨论】:
任何可以移出循环的东西通常对性能来说都是一件好事。但是它们并不完全等效,因为访问all
可能会引发异常并且两者之间的处理方式不同。此外,首先抛出异常可能会继续循环,具体取决于您在 catch 中执行的操作。
它们是不同的,例如,可能的执行路径是不同的。这就像说哪个更好:if() for()
vs for() if()
:它们有不同的用途。
@JoachimIsaksson 特别是当它可以speed up the code 时。 ;)
@PTwr 现在这很奇怪 :)
@JoachimIsaksson 如果您从汇编程序的角度来看,则不是(已经给出了很好的解释,所以我不会在这里打扰)。
【参考方案1】:
公平没有硬性规定,它是根据情况而定的。
这取决于您是要在其中一项导致问题时停止整个循环,还是只捕获该单个问题并继续。
例如,如果您正在向人们发送电子邮件,您不会希望在发送其中一个时发生异常时停止处理,但是如果您正在管理一组数据库事务并且需要在其中任何一个失败时回滚,也许在异常/问题时停止处理更可取?
【讨论】:
我应该补充一下 - 如果你不需要循环中的 try catch 并且可以将它放在外面,那么它会更好,因为它降低了复杂性【参考方案2】:根据要求,这是我很酷的答案。有趣的部分将在最后,所以如果您已经知道 try-catch 是什么,请随意滚动。 (对不起,部分题外话)
让我们从一般的 try-catch 概念开始。
为什么?因为这个问题表明缺乏完整的知识如何以及何时使用此功能。
什么是try-catch?或者更确切地说,什么是 try-catch-finally。
(本章也被称为:为什么你还没有使用谷歌了解它?)
尝试 - 可能不稳定的代码,这意味着您应该移动所有 稳定的部分。它总是执行,但没有保证 完成时间。
Catch - 在这里放置旨在纠正故障的代码 发生在 Try 部分。仅在发生异常时执行 尝试阻止。
最后——它的第三部分和最后一部分,在某些语言中可能没有 存在。它总是被执行。通常它用于释放 内存并关闭 I/O 流。
一般来说,try catch 是一种将可能不稳定的代码与程序的其余部分分开的方法。 就机器语言而言,它可以缩短为将所有处理器寄存器的值放在堆栈上以防止它们损坏,然后通知环境忽略执行错误,因为它们将由代码手动处理。
使用 try-catch 块的最佳做法是什么?
根本不使用它们。使用 try-catch 覆盖代码意味着您预计它会失败。为什么代码失败?因为写的不好。 编写不需要 try-catch 即可安全工作的代码,无论是性能还是质量都要好得多。
有时,尤其是在使用第三方代码时,try-catch 是最简单、最可靠的选择,但大多数时候在您自己的代码上使用 try-catch 表明存在设计问题。
例子:
数据解析 - 在数据解析中使用 try-catch 非常非常糟糕。有很多方法可以安全地解析最奇怪的数据。其中最丑陋的方法之一是正则表达式方法(遇到问题?使用正则表达式,问题喜欢使用复数形式)。字符串到 Int 的转换失败?首先检查你的数据,.NET 甚至提供了 TryParse 之类的方法。
除以零、精度问题、数值溢出 - 不要 用 try-catch 覆盖它,而不是升级你的代码。算术代码 应该从好的数学方程式开始。当然你可以大量修改 数学方程运行得更快(例如通过 0x5f375a86),但您仍然需要良好的数学基础。
列表索引越界、堆栈溢出、分段错误、 Heartbleed - 在代码设计中你有更大的错误。那些 错误不应该发生在正确编写的代码中运行 健康的环境。他们都遇到一个简单的错误,代码有 确保索引(内存地址)在预期的边界内。
I/O 错误 - 在尝试使用流之前(内存、文件、 网络),第一步是检查流是否存在(不为空,文件 存在,连接打开)。然后你检查流是否正确 - 是 你的索引在它的大小?流准备好使用了吗?是它的队列/缓冲区 容量足以容纳您的数据?这一切都可以在没有的情况下完成 单试抓住。特别是当您在框架下工作时(.NET, Java 等)。
当然还有意外访问问题的问题 - 老鼠 咀嚼你的网线,硬盘驱动器融化了。这里的用法 try-catch 不仅可以被原谅,而且应该发生。不过还是需要 以适当的方式完成,例如this example for files。 您不应该将整个流操作代码放在 try-catch 中, 而是使用内置方法来检查其状态。
错误的外部代码 - 当您开始使用糟糕的代码库时, 没有任何纠正方法(欢迎来到企业界), try-catch 通常是保护其余代码的唯一方法。但还 同样,只有直接危险的代码(调用可怕的 写得不好的库中的函数)应该放在try-catch中。
那么什么时候应该使用 try-catch,什么时候不应该呢?
可以用非常简单的问题来回答。
我可以将代码更正为不需要 try-catch 吗?
是吗?然后删除该 try-catch 并修复您的代码。
没有?然后在 try-catch 中打包不稳定的部分,并提供良好的错误处理。
如何处理 Catch 中的异常?
第一步是了解会发生什么类型的异常。现代环境提供了在类中隔离异常的简单方法。尽可能捕捉最具体的异常。做 I/O?捕捉 I/O。做数学?赶上算术的。
用户应该知道什么?
只有用户可以控制的:
网络错误 - 检查您的电缆。 文件 I/O 错误 - 格式 c:. 内存不足 - 升级。其他异常只会告诉用户你的代码写得有多糟糕,所以坚持神秘的内部错误。
Try-catch 在循环内还是在循环外?
正如很多人所说,这个问题没有明确的答案。这完全取决于您提交的代码。
一般规则可以是: 原子任务,每次迭代都是独立的——循环内的try-catch。 链式计算,每次迭代都依赖于之前的迭代 - 循环尝试捕获。
for 和 foreach 有何不同?
Foreach 循环不保证按顺序执行。听起来很奇怪,几乎从未发生过,但仍有可能。如果您将 foreach 用于创建它的任务(数据集操作),那么您可能希望在它周围放置 try-catch。但如前所述,您应该尽量不要过于频繁地使用 try-catch。
有趣的部分!
亲爱的读者,这篇文章的真正原因只是你的几行!
根据弗朗辛·德格鲁德·泰勒的要求,我会写更多有趣的部分。 请记住,正如 Joachim Isaksson 所注意到的,乍一看很奇怪。
虽然这部分将侧重于 .NET,但它可以适用于其他 JIT 编译器,甚至部分适用于汇编。
那么.. try-catch around loop 怎么可能加快它的速度? 这没有任何意义!错误处理意味着额外的计算!
查看有关它的 *** 问题:Try-catch speeding up my code? 你可以在那里阅读 .NET 特定的东西,在这里我将尝试专注于如何滥用它。 请记住,这个问题来自 2012 年,因此它也可以在当前的 .NET 版本中“更正”(这不是错误,而是一个功能!)。
如上所述,try-catch 将一段代码与其余部分分开。 分离过程的工作方式与方法类似,因此您也可以将计算量大的循环放在单独的方法中,而不是 try-catch。
如何分离代码可以加快速度?寄存器。网络比 HDD 慢,HDD 比 RAM 慢,与超快 CPU Cache 相比,RAM 是一个慢动作。而且还有CPU Registers,嘲笑Cache有多慢。
分离代码通常意味着释放所有通用寄存器——而这正是 try-catch 所做的。或者更确切地说,由于 try-catch,JIT 正在做什么。
JIT 最突出的缺陷是缺乏预知能力。它看到循环,它编译循环。当它最终注意到循环将执行数千次并吹嘘使 CPU 吱吱作响的计算时,释放寄存器为时已晚。所以必须编译循环中的代码以使用寄存器的剩余部分。
即使是一个额外的寄存器也可以极大地提高性能。每次内存访问都非常长,这意味着 CPU 可以在相当长的时间内未使用。虽然现在我们得到了乱序执行、可爱的管道和预取,但仍然存在强制代码停止的阻塞操作。
现在让我们谈谈为什么与 x64 相比,x86 很烂而且很垃圾。在为 x64 编译时,链接 SE 问题中的 try-catch 速度提升没有发生,为什么?
因为一开始就没有速度增益。存在的只是糟糕的 JIT 输出导致的速度损失(经典编译器没有这个问题)。 Try-catch 纠正 JIT 行为大多是偶然的。
为certain tasks 创建了x86 寄存器。 x64 架构将它们的大小翻了一番,但它仍然无法改变这样一个事实,即在执行循环时必须牺牲 CX,其他寄存器也是如此(除了可怜的孤儿 BX)。
那么为什么 x64 如此出色?它拥有 8 个额外的 64 位宽寄存器,没有任何特定用途。您可以将它们用于任何事情。不仅理论上与 x88 寄存器一样,而且实际上适用于任何东西。 8 个 64 位寄存器意味着 8 个 64 位变量直接存储在 CPU 寄存器中,而不是 RAM 中,进行数学运算没有任何问题(通常需要 AX 和 DX 来获得结果)。 64位也意味着什么? x86 可以将 Int 放入寄存器,x64 可以放入 Long。如果数学块有空寄存器可以工作,它可以在不接触内存的情况下完成大部分工作。这才是真正的速度提升。
但这还没有结束!你也可以滥用缓存。 Cache 离 CPU 越近,它变得越快,但它也会更小(成本和物理大小是限制)。如果您将优化数据集以立即填充缓存,例如。日期块的大小为 L1 的一半,将另一半留给代码和任何 CPU 在缓存中发现的必要内容(除非您使用汇编,否则您无法真正优化它,在高级语言中您必须“猜测”)。通常每个(物理)核心都有自己的 L1 内存,这意味着您可以一次处理多个缓存块(但创建线程并不总是值得开销)。
值得一提的是,旧的 Pascal/Delphi 在 32 位处理器时代在几个重要功能中使用了“16 位恐龙”(这使得它们比 C/C++ 的 32 位处理器慢两倍)。所以爱你的 CPU 寄存器,即使是可怜的旧 BX。他们非常感激。
再补充一点,因为这篇文章已经变得相当疯狂,为什么 C#/Java 可以同时比原生代码更慢和更快? JIT 就是答案,框架代码 (IL) 被翻译成机器语言,这意味着长计算块将像 C/C++ 的本机代码一样执行。但是请记住,您可以轻松地在 .NET 中使用本机组件(在 Java 中,您可能会因为尝试而发疯)。对于足够复杂的计算,您可以通过本机代码的速度增益来覆盖切换托管本机模式的开销(并且本机代码可以通过 asm 注入来提升)。
【讨论】:
“使用 try-catch 块的最佳实践是什么?根本不使用它们”——然后您继续列出 ~3 个实际应该使用 try-catch 的示例。对于正在寻找简单答案的新手,请删除它......(简单的答案是:有时,绝对,你需要 try-catch。)我会惊讶地看到即使是最简单的项目也绝对没有 try-catch ....它一定不是在做一些非常困难或有趣的事情 imo 感谢您的回答,因为它可以让您更深入地了解计算机内部的实际情况。对此表示赞同。【参考方案3】:两种情况下的性能可能相同(但如果您想确定,请运行一些测试)。异常检查仍然在每次循环中发生,只是在被捕获时跳转到其他地方。
不过,行为是不同的。
在第一个示例中,将捕获一个项目的错误,然后循环将继续针对其余项目。
在第二种情况下,一旦遇到错误,循环的其余部分将永远不会执行。
【讨论】:
【参考方案4】:这取决于你想要实现什么:) 第一个将尝试循环中的每个函数,即使其中一个会失败,rest 也会运行......第二个将中止整个循环,即使出现一个错误......
【讨论】:
【参考方案5】:在第一个示例中,循环在捕获发生后继续(除非您告诉它中断)。也许您想在需要将错误数据收集到列表中(在捕获中要做的事情)以在最后向某人发送电子邮件并且不想停止整个过程的情况下使用它出错了。
在第二个示例中,如果您希望它在出现问题时立即中断以便您进行分析,它将阻止循环的其余部分发生。
【讨论】:
【参考方案6】:您更应该关注您想要的行为而不是性能。考虑您是否希望在发生异常时能够继续循环,以及在哪里进行清理。在某些情况下,您可能希望在循环内部和外部都捕获异常。
try...catch
对性能的影响很小。您所做的大多数实际上可能导致异常的事情所花费的时间比设置捕获异常所需的时间要长得多,因此在大多数情况下,性能差异可以忽略不计。
在任何情况下,如果存在可衡量的性能差异,您将在循环内做很少的工作。通常在这种情况下,您无论如何都希望try...catch
在循环之外,因为循环内没有任何需要清理的东西。
【讨论】:
【参考方案7】:对于大多数应用程序而言,性能差异可以忽略不计。
但是问题是,如果一个失败,您是否要继续处理循环中的其余项目。如果是,则使用内部 foreach,否则使用单个外部循环。
【讨论】:
以上是关于在 foreach 循环中使用 try-catch 块的最佳做法是啥? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章
面试官:try-catch放在循环体内还是循环体外,哪种效率更高?
编写高质量代码改善C#程序的157个建议——建议64:为循环增加Tester-Doer模式而不是将try-catch置于循环内