【发布时间】:2014-09-04 15:57:46
【问题描述】:
由于性能原因,c# 中的算术溢出检查似乎默认关闭(参见https://stackoverflow.com/a/108776/2406234 等)。
但是如果我打开它,无论是使用 /checked 标志还是 checked 关键字,运行时实际上是如何执行检查的? (我试图更好地理解这种“性能影响”的含义。)
【问题讨论】:
标签: c#
由于性能原因,c# 中的算术溢出检查似乎默认关闭(参见https://stackoverflow.com/a/108776/2406234 等)。
但是如果我打开它,无论是使用 /checked 标志还是 checked 关键字,运行时实际上是如何执行检查的? (我试图更好地理解这种“性能影响”的含义。)
【问题讨论】:
标签: c#
算术运算,一直到硬件级别,都支持指示所执行的运算是否导致溢出。在许多情况下,这些信息将被简单地忽略(并且通常会在非常低的级别被忽略),但是可以在每次操作后检查此结果,如果发生溢出,则抛出一个新异常。所有这些检查,以及通过各个抽象层传播这些信息,当然是有代价的。
【讨论】:
C# 中的溢出检查通过在 CIL 中使用(或不使用)溢出检查来工作。
以 C# 代码为例:
public static int AddInts(int x, int y)
{
return x + y;
}
如果不进行溢出检查,它将被编译为:
.method public hidebysig static int32 AddInts(int32 x, int32 y) cil managed
{
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: add
IL_0003: ret
}
通过溢出检查,它将被编译为:
.method public hidebysig static int32 AddInts(int32 x, int32 y) cil managed
{
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: add.ovf
IL_0003: ret
}
如您所见,CIL 表单使用add 的不同溢出检查和非溢出检查形式,同样适用于checked 和unchecked 影响C# 中行为的每个操作。这在一段混合了许多检查和未检查操作的代码中会更方便,但绝大多数情况下,大多数情况下在方法中将检查在一起或未检查在一起,因此 C# 的程序集默认方法是在一个块中覆盖几乎总是对人类编码人员更方便。
当这个 CIL 被 jitted 时会发生什么当然取决于它被 jitted 到的处理器。一个可能的结果是使用了类似的溢出检查指令(这将导致溢出中断,这可用于产生 .NET 在这里想要的异常)或像 x86 的 jo 这样的溢出跳转指令。
我正试图更好地了解这种“性能影响”的含义。
确实,unchecked 几乎总是和checked 一样快,而且通常更快,因为忽略溢出可以让路径更有效,而不是阻止。简而言之,检查溢出有时需要另一个操作,尽管大多数时候在低级别非常快,并且做某事几乎总是比什么都不做要慢。*因此,在您知道不会发生溢出的情况下,它在不检查不可能的情况下确实有轻微的性能优势。
但是,这应该被视为unchecked 的次要功能。
主要特点是它改变了算术运算的含义意思。
对于两个 32 位整数(例如),unchecked(x + y) 表示“将 x 和 y 相加并将结果强制转换为 32 位二进制补码”,而checked(x + y) 表示“将 x 和 y 相加并返回产生的 32 位二进制补码数”。
因此,当unchecked(int.MaxValue + int.MaxValue) 返回-2 时,这是正确答案,而checked(int.MaxValue + int.MaxValue) 没有正确答案,因此会引发例外。
它们确实是不同的操作,在很多情况下我们想要-2 第一个返回。
因此,我们主要关心的是“如果我们超出类型的限制,正确的答案是什么?”
unchecked。checked。long 或BigInteger 而不是int,这样您就可以正确处理所有可能性. (如果性能分析显示它有显着帮助,也许只需要 int 就可以快速路径)。checked 使用,但 unchecked 会得到相同的结果,因此我们可以使用它来获得轻微的性能提升。人们与每个人打交道的频率取决于他们的编程目的。可能大多数程序最常处理第四种情况(你的程序多久处理一次超过几百万的事情?),我们应该在语义上使用checked,但它没有真正的区别,所以我们不妨获得unchecked 的性能提升。
第一种情况在某类情况下很常见,尤其是比较低级的代码;通常,程序算术在其大多数“业务逻辑”中都适合第四种情况,而在许多低级库正在处理的东西中,第一种情况。
当我们需要一些接近“真实世界”数学的算术并且int 无法解决时,我们通常仍然需要“真实”结果,所以我们处于第三种情况的某种变体中。
真的,做一些算术并偶尔说“对不起,我无法处理这个”的第一种情况很少是理想的行为; OverflowException 通常是一种异常,它告诉开发人员他们在哪里遇到问题,而不是我们捕获的那种异常,然后变成对用户有帮助的错误消息。因此,大多数情况下,当我们遇到第一个案例时,我们认为我们遇到了第四个案例,但我们错了。
因此,它可以用于:
unchecked,即使这对于项目默认值来说是多余的。OverflowException 的代码标记为checked,因为你会用它(很少见)做一些有用的事情。unchecked 来提高性能,但始终使用checked 进行调试,偶尔运行您的单元测试,或者如果您有一些奇怪的行为。 (此处需要谨慎平衡的地方取决于应用程序)。*也有可能发生分支错误预测,尽管非溢出情况很可能是最常预测的情况,也是最常遇到的情况。一般来说,分支错误预测在这里的风险并不像在其他情况下那样大,包括手动检查溢出。
【讨论】: