CLR VS OCaml的异常开销(CLR vs OCaml exception overhead)

2019-09-17 23:19发布

阅读开始F# -罗伯特·皮克林我专注于以下内容:

程序员从OCaml的背景来车时应当使用F#异常时要小心。 由于CLR的体系结构,抛出异常是相当昂贵的,相当比OCaml的更贵一点。 如果你扔了很多异常,仔细分析代码来决定性能的成本是否值得。 如果成本过高,适当地修改代码。

为什么呢,因为CLR的,抛出异常是比较昂贵的,如果F#OCaml ? 什么是在这种情况下,适当地修改代码的最佳方式?

Answer 1:

里德已经解释了为什么.NET异常行为不同于OCaml的例外。 在一般情况下,.NET例外是只适用于特殊情况下,设计用于这一目的。 OCaml有更轻巧的模型,所以他们使用,也能实现一定的控制流模式。

举一个具体的例子,OCaml中可以使用异常实现循环的断裂。 例如,假设你有一个功能test ,测试一个数是否是一个数字,我们想要的。 以下迭代数从1到100,并返回第一匹配数:

// Simple exception used to return the result
exception Returned of int

try
  // Iterate over numbers and throw if we find matching number
  for n in 0 .. 100 do
    printfn "Testing: %d" n
    if test n then raise (Returned n)
  -1                 // Return -1 if not found
with Returned r -> r // Return the result here

要实现这一点没有例外,你有两个选择。 你可以写一个具有相同特性的递归函数-如果你打电话find 0 (和它被编译到基本相同的IL代码使用return nfor在C#中环):

let rec find n = 
  printfn "Testing: %d" n
  if n > 100 then -1  // Return -1 if not found
  elif test n then n  // Return the first found result 
  else find (n + 1)   // Continue iterating

使用递归函数的编码可能有点过长的,但你也可以使用由F#库提供的标准功能。 这通常是重写代码,将使用OCaml的异常控制流的最佳方式。 在这种情况下,你可以这样写:

// Find the first value matching the 'test' predicate
let res = seq { 0 .. 100 } |> Seq.tryFind test
// This returns an option type which is 'None' if the value 
// was not found and 'Some(x)' if the value was found.
// You can use pattern matching to return '-1' in the default case:
match res with
| None -> -1
| Some n -> n

如果你不熟悉的选项类型,再看看一些介绍材料。 F#维基有一个很好的教程和MSDN文档有有用的例子太多。

使用来自适当的函数Seq模块往往使得代码短了很多,所以它是preferrable。 它可能会稍微比直接使用递归效率较低,但在大多数情况下,你不需要担心。

编辑:我很好奇的实际性能。 在使用版本Seq.tryFind是更有效的,如果输入被懒惰地生成的序列seq { 1 .. 100 }而不是一个列表[ 1 .. 100 ]因为列表分配的成本的)。 有了这些变化,并test函数,返回25元,时间需要我的机器上运行代码100000次:

exceptions   2.400sec  
recursion    0.013sec  
Seq.tryFind  0.240sec

这是非常琐碎的样本,所以我想使用该解决方案Seq一般不会跑的比使用递归编写的等效代码慢10倍。 经济放缓可能是由于其他数据结构的分配(对象代表序列,关闭,...),也由于额外的间接(代码需要大量的虚方法调用,而不只是简单的数字运算和跳跃)。 然而,例外是更昂贵和不使代码以任何方式更为简单和易读...



Answer 2:

在CLR异常是非常丰富的,并提供了很多细节。 波多黎各马里亚尼上公布的(旧,但仍适用)后异常的成本在其中详细介绍一些这方面的CLR。

这样,在CLR抛出异常具有比在其他一些环境中,包括OCaml的较高的相对成本。

什么是在这种情况下,适当地修改代码的最佳方式?

如果你希望异常在正常,非特殊情况下得到提升,你可以重新考虑你的算法和API在完全避免了异常的方式。 试图提供一种替代的API,其中可以测试之前使异常被提出,例如的情况。



Answer 3:

为什么呢,因为CLR的,抛出异常是比较昂贵的,如果F#比OCaml中?

OCaml的沉重的使用异常作为控制流的优化。 相反,在.NET异常尚未在所有优化。

需要注意的是性能上的差异是巨大的 。 例外的是在OCaml的快大约600×比他们在F#。 甚至C ++是在这方面比OCaml的慢大约6×,根据我的基准。

尽管.NET异常涉嫌提供更多(的OCaml提供堆栈跟踪,你还要什么?)我想不出任何理由,他们应该是因为他们是一样慢。

什么是在这种情况下,适当地修改代码的最佳方式?

在F#中,你应该写“总”的功能。 这意味着你的函数应该返回指示样的结果的联合类型的值,例如正常或异常。

特别是,调用find应该将呼叫更换tryFind返回的值option类型要么是Some价值或None ,如果关键因素是不存在的收藏。



文章来源: CLR vs OCaml exception overhead