【问题标题】:Does using single-case discriminated union types have implications on performance?使用单例区分联合类型是否会对性能产生影响?
【发布时间】:2013-09-19 20:19:39
【问题描述】:

最好为每个原始值都有一个包装器,这样就没有办法滥用它。我怀疑这种便利是有代价的。有没有性能下降?如果性能是一个问题,我应该使用裸原始值吗?

【问题讨论】:

    标签: f# variant


    【解决方案1】:

    从 F# 4.1 开始,将 [<Struct>] 属性添加到合适的单大小写区分联合将提高性能并减少执行的内存分配次数。

    【讨论】:

      【解决方案2】:

      Jack 在编写高性能 F# 代码方面的经验比我多,所以我认为他的回答绝对正确(我也认为使用度量单位的想法很有趣)。

      为了把事情放在上下文中,我编写了一个非常基本的测试(仅使用 F# Interactive - 所以在发布模式下可能会有所不同)来比较性能。它分配一个包装(相对于非包装)int 值的数组。这可能是非包装类型确实是一个不错的选择的场景,因为数组将只是一个连续的内存块。

      #time
      // Using a simple wrapped `int` type
      type Foo = Foo of int
      let foos = Array.init 1000000 Foo
      // Add all 'foos' 1k times and ignore the results
      for i in 0 .. 1000 do 
        let mutable total = 0
        for Foo f in foos do total <- total + f
      

      在我的机器上,for 循环平均需要大约 1050 毫秒。现在,展开的版本:

      let bars = Array.init 1000000 id    
      for i in 0 .. 1000 do 
        let mutable total = 0
        for b in bars do total <- total + b
      

      在我的机器上,这大约需要 700 毫秒。

      因此,肯定会有一些性能损失,但可能比预期的要小(大约 33%)。这是一个测试,除了在循环中展开值之外几乎什么都不做。在做一些有用的事情的代码中,开销会小很多。

      如果您正在编写高性能代码、处理大量数据的代码或需要一些时间并且用户会频繁运行它的代码(例如编译器和工具),这可能是个问题。另一方面,如果您的应用程序不是性能关键,那么这不太可能是一个问题。

      【讨论】:

      • C# 中没有可区分联合这样的东西。可以使用访问者模式来代替,这涉及每次需要将抽象实例解析为具体实例时创建访问者并进行虚拟函数调用(可能不止一个)。我不知道歧视工会的内部调度机制是什么。我希望我知道这一点,这样我就可以在使用它们时做出更有意识的决定。如果您对此有任何见解,我将不胜感激。
      • 有人反编译了一个有区别的联合。它确实基于继承,尽管调度是通过检查布尔属性和类型大小写(我怀疑)完成的,这在少数情况下很好,但在大量情况下可能会成为问题。 bugfree.dk/blog/2012/06/23/…
      • 模式匹配是使用Tag 属性编译的(所以它本质上是一个整数值上的switch)。这对于多种情况来说非常好 - 有区别的联合让您可以轻松添加新功能,这是使用虚拟方法无法实现的。访问者的主要好处是可维护性——这对于编译器生成的代码来说不是问题。
      • Array.init 1000000 (fun n -&gt; Foo -n) |&gt; Array.sortArray.init 1000000 (fun n -&gt; -n) |&gt; Array.sort 慢 90 倍。
      • @TomasPetricek:问题似乎是Array.sort 没有被定义为let inline sort xs = Array.sortWith compare xs,而是丢弃编译时类型信息,然后在运行时使用泛型比较。
      【解决方案3】:

      是的,当使用单例联合类型来包装原始值时,性能会下降。联合案例被编译到类中,因此您将支付分配(以及随后收集)类的代价,并且每次获取联合案例中保存的值时,您还将获得额外的间接。

      根据您的应用程序的具体情况,以及您多久会产生这些额外的开销,如果它使您的代码更安全和更模块化,它可能仍然值得做。

      我在 F# 中编写了 很多 对性能敏感的代码,我个人的偏好是尽可能使用 F# 度量单位类型来“标记”原始类型(例如,int )。这可以防止它们被滥用(感谢 F# 编译器的类型检查器),但也避免了任何额外的运行时开销,因为在编译代码时会删除度量类型。如果你想要一些这样的例子,我在我的fsharp-tools 项目中广泛地使用了这种设计模式。

      【讨论】:

      猜你喜欢
      • 2010-11-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-02
      • 1970-01-01
      • 2018-01-28
      相关资源
      最近更新 更多