如何在GHC中编写尽可能高效的数据结构?

46
有时候我需要编写一个在Hackage上找不到的数据结构,或者我找到的数据结构没有经过足够的测试或质量不能令我信任,或者这只是我不想依赖的东西。我现在正在阅读Okasaki的书,它非常好地解释了如何设计渐进快速的数据结构。
然而,我特别使用GHC工作。对于我的应用程序来说,常数因素非常重要。内存使用对我也很重要。因此,我有一些关于GHC的问题。
具体而言,
  • 如何最大程度地共享节点
  • 如何减少内存占用
  • 如何避免由于不当的严格/惰性导致的空间泄漏
  • 如何让GHC为重要部分的代码生成紧密的内部循环
我在网上找了各种地方,对于如何使用GHC,例如查看核心输出,使用UNPACK pragma等,我有一个模糊的想法。但是我不确定我是否理解正确。
所以我打开了我最喜欢的数据结构库containers,并查看了Data.Sequence模块。我不能说我理解他们做Seq变快的大部分内容。
首先吸引我眼球的是FingerTree a的定义。我觉得这只是我对finger tree不熟悉。第二件引起我注意的事情是所有的SPECIALIZE pragma。我不知道这里发生了什么,我非常好奇,因为它们在整个代码中都有很多。
许多函数还具有与之关联的INLINE pragma。我可以猜测这意味着什么,但是我如何判断何时内联函数?

在大约第475行左右,有一个标题为“可应用构造”的部分,非常有趣。他们定义了一个新类型包装器来表示Identity单子,编写了自己的严格状态单子的副本,并定义了一个名为applicativeTree的函数,显然是专门用于Identity单子的,这增加了函数输出的共享率。我不知道这里到底发生了什么。使用了什么魔法来增加共享呢?

无论如何,我不确定从Data.Sequence中有多少可以学习的东西。是否还有其他“样板程序”可以阅读以获取经验?我真的很想知道如何加速我的数据结构。特别是编写易于融合的数据结构,以及如何编写好的融合规则。


21
如果我的记忆没错的话,对于这个问题的标准答案是“将你的问题加入语言基准测试并等待Don为你进行优化”。;] - C. A. McCann
哈哈哈。也许我应该吃掉Dons,这样就能获得他惊人的能力,创造与C相竞争的Haskell代码了。 - danharaj
2个回答

41

这是一个很大的话题!大部分内容已经在其他地方解释过了,所以我不会在这里写一章书。以下是一些资源:

  • 《Real World Haskell》第25章,"性能" - 讨论性能分析、简单特化和展开、Core 代码读取以及一些优化。

Johan Tibell 在这个主题上写了很多东西:

还有一些来自这里的东西:

  • 一般优化
  • 更多关于解包的内容
  • 解包和严格性
  • 还有其他一些内容:


    那么,专门的编译指示符是否类似于您对数据结构的优化,但是自动完成并应用于函数? - danharaj
    是的。虽然有INLINE存在,但这有点不必要。 - Don Stewart
    7
    SPECIALIZE 比 INLINE 更少出现代码膨胀,因为每种类型只会有一个额外的函数体,而不是每个调用点都有一个额外的函数体。我的经验法则是:在具有高阶参数的函数上使用 INLINE(以避免间接调用),否则使用 SPECIALIZE。随着 INLINABLE 的添加,我几乎在以前使用 SPECIALIZE 的所有情况下都使用它,因为 INLINABLE 允许在不同模块中进行特化。 - tibbe
    2
    我的“高性能Haskell”演讲在CUFP包含了“关于惰性求值的推理”的幻灯片的超集。您可以在此处找到它:http://blog.johantibell.com/2010/09/slides-from-my-high-performance-haskell.html - tibbe
    1
    Edward Zhang在他的博客中发表了一篇有趣的文章,介绍了一种树形映射的模式,使得当函数结果相同时可以获得最大化的共享。 - user810057

    13

    applicativeTree相当高级,但主要是与FingerTrees有关,它们本身就是一种高级数据结构。我们在cstheory上讨论了一些细节问题。请注意,applicativeTree的编写是为了适用于任何 Applicative。只不过当它特化为Id时,它可以以一种其他情况下做不到的方式共享节点。您可以通过内联Id方法并观察结果来自己完成这个特化过程。请注意,这个特化仅在一个地方使用——O(log n)replicate函数中。更一般的功能完美地特化为常数情况,这是非常聪明的代码重用,但也仅此而已。

    总体而言,Sequence 更多地教授如何设计持久化数据结构,而不是使用各种技巧来提高性能。Dons'的建议当然很好。我还建议浏览一下真正规范和调优的库的源代码——特别是MapIntMapSetIntSet。除此之外,值得一看的还有Milan关于他对containers库改进的论文


    3
    HashMap和HashSet,我的新最爱。 - augustss
    @augustss:我同意,除了当你浏览源代码时,HashMap和HashSet就像Milan的论文所记录的那样,基本上只是Maps和Sets的薄包装(但非常出色)。 - sclv
    3
    你看过Johan的哈希表吗?http://hackage.haskell.org/packages/archive/unordered-containers/0.1.3.0/doc/html/src/Data-HashMap-Common.html#HashMap - Don Stewart
    @Don -- 哦,当然。那是我犯的一个错误。 - sclv
    1
    我曾经编写过applicativeTree实现,而sclv说得对,它主要存在是因为它很聪明。= P 是的,它确实巧妙地概括了使复制函数O(log n)的方法,但这不是大多数数据结构都适用的技巧。 - Louis Wasserman

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接