【问题标题】:Why systematically mark record fields strict in Haskell?为什么在 Haskell 中系统地标记记录字段?
【发布时间】:2018-05-24 06:25:26
【问题描述】:

我已多次阅读,在 Haskell 中系统地标记记录的字段 strict 是一种很好的做法,例如而不是

data Foo = Foo { bar :: Bar, quux :: Quux }

data Foo = Foo { bar :: !Bar, quux :: !Quux}

例如,这是来自Haskell Programming from First Principles,第1134页的引用:

要遵循的一个好规则是脊椎懒惰,叶子严格!

我(想我)明白什么是严格以及两者之间的区别。我不明白的是为什么后者系统地这样做?

【问题讨论】:

  • 您能否添加对您阅读的地方的引用,这是一个很好的做法systematically。谢谢。
  • 我不会系统地那样做。但是,在某些情况下,在叶子中保持严格确实有助于提高性能,尤其是对于像 Char,Int,Bool,() 这样的小型数据类型。 FWIW,我通常让一切变得懒惰,然后在分析我的代码时尝试将事情变得更严格,看看是否有改进。
  • 我倾向于认为记录字段应该是严格的,除非有理由让它们变得懒惰,但相反的立场是可以辩护的。使字段变得惰性的一个原因是其类型未知(因此可能是函数或类似的)。
  • 据我所知,您希望对“小”值进行严格处理,因为不必要的懒惰会导致内存泄漏。在许多情况下,评估 thunk 并存储结果比保留大量不必要的 thunk 更便宜。
  • 那里有很多货物崇拜的严格性。偶尔它很好,但它通常会导致像我这样的人提交错误报告,说你的类型和函数太严格了。

标签: haskell


【解决方案1】:

一个常见的经验法则是在以下情况下使数据结构严格:

  • 您希望严格遍历整个事物并保留它,因此懒惰的开销没有意义;或

  • 字段“小”——小于或等于指针的大小——您希望编译器将它们拆箱以避免不必要的间接寻址。 (-funbox-small-strict-fields 自 GHC 7.7 起默认开启。)

这在实践中往往与“脊椎懒惰,叶子严格”大致相同,因为数据结构通常会从懒惰中受益,但它们的内容通常不会。 p>

“系统地”执行此操作是帮助避免空间泄漏的一种简单方法 - 例如,如果您重复修改数据结构(例如累加器)而不同时强制值:

ghci> data Lazy = Lazy { lazyField :: Int } deriving (Show)

ghci> data Strict = Strict { strictField :: !Int } deriving (Show)

ghci> modifyLazy (Lazy field) = Lazy { lazyField = field + 1 }

ghci> modifyStrict (Strict field) = Strict { strictField = field + 1 }

ghci> lazy = iterate modifyLazy (Lazy 0) !! 1000000

ghci> strict = iterate modifyStrict (Strict 0) !! 1000000

ghci> :set +s

ghci> lazy
Lazy {lazyField = 1000000}
(0.76 secs, 251,792,080 bytes)

ghci> strict
Strict {strictField = 1000000}
(0.52 secs, 178,173,544 bytes)

lazy 版本在强制执行之前会建立一个 thunk 链;严格版本会在每个步骤中对 Int 字段进行全面评估。 modifyIORef(惰性)和modifyIORef'(严格)也会发生类似的情况。

在具有非严格类型的字段上添加严格注释几乎没有意义,例如,field :: ![Int] 仅确保 first (:)[] 构造函数是强制的;它不会使整个列表变得严格。如果你需要,你可能需要一个严格的序列类型,比如Data.Vector。并且通常不建议将多态字段设置为严格,如data Foo a = Foo … !a …,因为使用该类型的人可能期望能够依赖那里的惰性——例如,在使用循环的算法中(“打结”) ——而且不得不将类型包装在像data Lazy a = Lazy a 这样的额外构造函数中以重新获得懒惰,这很烦人。懒惰也更适合等式推理——尽管在实践中我们经常使用忽略严格性和非全函数的“快速和松散”的等式推理。

最终,决定某些东西应该是惰性还是严格的唯一方法是考虑特定应用程序所需的语义,并添加明智的严格性(字段上的刘海,BangPatternsseq,严格的函数,如 @ 987654336@) 如果您遇到性能问题或空间泄漏,则在分析之后。

【讨论】:

  • 我的问题源于我从不不必担心它,正如我所说,我多年来一直在编写 Haskell。我怀疑我的性能要求可能不够高,无法注意到问题,因此修复它。
  • @insitu:尝试在启用分析的情况下运行您的代码一段时间,然后查看堆图。如果它看起来像/|,在程序执行过程中构建了很多(即,远远超过您正在使用的数据的大小)并且只在最后下降,那么您可能有空间泄漏 -然后您可以使用类型和保持器分析来定位它。它通常不会对短期程序造成严重的性能问题,除非峰值内存使用率非常高; GHC Haskell 的性能通常非常好,但这确实取决于应用程序。
  • 谢谢。我通常编写长时间运行的服务器应用程序(例如后端)并使用 GHC 分析,但从未遇到过这个问题,或者至少从未注意到它。
  • @JonPurdy 你能给出这个 type & retainer 分析技术的链接吗?我从来没有听说过这个。这是一个不同的名称:neilmitchell.blogspot.sg/2015/09/detecting-space-leaks.html
  • @Shersh:GHC Users Guide section on profiling 具有编译器和运行时选项列表,包括用于基于类型的内存分析的 -hy 和用于保留器分析的 -hr
猜你喜欢
  • 2014-05-13
  • 1970-01-01
  • 2022-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-17
  • 1970-01-01
  • 2012-01-07
相关资源
最近更新 更多