【问题标题】:Preventing observable sharing for certain subtrees in Haskell防止 Haskell 中某些子树的可观察共享
【发布时间】:2014-10-06 03:14:06
【问题描述】:

我有一个 EDSL,它为数组提供类似列表的组合器(mapzipWith 等)

一些组合器要求某些输入是随机访问的。例如。 Gather 的数据数组在另一个指定的索引处从数据数组中选取元素:

Gather (Manifest [10,11,12]) (Manifest [2,0,0,1,2]) = [12,10,10,11,12]

该语言使用data-reify 包来恢复共享。问题是有时同一子树包含需要提供随机访问的节点和可以顺序计算的节点。让他们共享会破坏后续的评估者。

例如,在下面的树中,我希望 [1,2,3] 继续被共享,但 Manifests 将它们包装为恢复图中的不同节点:

     [1, 2, 3]
     /       \
 Manifest Manifest
    |        |
    |     Map (+1)
    \     /
    Gather

一个更复杂的示例可能包括许多共享节点,所有这些节点都应该变得不同(即使 Map (+1) (Manifest [1,2,3]) 可以共享。

     [1, 2, 3]
     /       \
 Manifest Manifest
    |        |
 Map (+1)  Map (+1)
    |       /|
 Map (*2)  / |
    \     /  |
    Gather   |
        \    |
        ZipWith (+)

即使我找到了简单案例的解决方案(Gather 直接引用Manifest),它已经涵盖了大部分用例。

欢迎大家指点!

下面是该语言的简单模型。

module NoSharing where

data AST = Manifest [Int]
         | Map (Int -> Int) AST
         | ZipWith (Int -> Int -> Int) AST AST
         | Gather AST  -- ^ Data
                  AST  -- ^ Indices

complex = ZipWith (+) gathered indexes
  where
    gathered = Gather (Map (*2) indexes) indexes
    indexes  = Map (+1) $ Manifest [1,2,3]


simple = Gather dat indexes
  where
    dat     = Manifest [1,2,3]
    indexes = Map (+1) dat

【问题讨论】:

  • 你能识别出不应该被构造函数共享的东西,例如所有Manifests 都不应该共享?
  • 任何依赖可观察共享的东西都注定是脆弱的,因为 Haskell 没有可观察共享。一些实现允许您通过肮脏的技巧恢复一些共享。如果您希望它可靠,则需要在 DSL 中明确共享。
  • @augustss Observable 共享可能会产生误报,而不是误报,所以它不会破坏任何东西。该程序只会运行得更慢。这似乎是最容易实现的事情。我可能仍然能够以纯粹的方式恢复隐式共享,因为这些术语可以在结构上进行比较。

标签: haskell ghc data-reify


【解决方案1】:

您可以这样做的一种方法是在致电data-reify 之前手动取消共享。例如,这个函数应该希望取消共享顶级 Manifest 构造函数,但保留其参数共享:

rebuildManifest :: AST -> AST
rebuildManifest (Manifest xs) = Manifest xs
rebuildManifest t = t

现在要取消共享Gather 下的任何Manifest,您可以递归地执行相同的操作,注意在适当的时候重用原始文件

rebuildAllManifestsUnderGather :: AST -> (AST, Bool)

rebuildAllManifestsUnderGather t@(Map f t') =
    let (newt', reuse) = rebuildAllManifestsUnderGather t'
    in if reuse then (t, True) else (Map f newt', False)

rebuildAllManifestsUnderGather t@(ZipWith f t1 t2) =
    let (newt1, reuse1) = rebuildAllManifestsUnderGather t1
        (newt2, reuse2) = rebuildAllManifestsUnderGather t2
    in if reuse1 && reuse2 then (t, True) else (ZipWith f newt1 newt2, False)

rebuildAllManifestsUnderGather t@(Gather t1 t2) =
    let (newt1, reuse1) = rebuildManifest $ rebuildAllManifestsUnderGather t1
        (newt2, reuse2) = rebuildManifest $ rebuildAllManifestsUnderGather t2
    in if reuse1 && reuse2 then (t, True) else (Gather newt1 newt2, False)

    where rebuildManifest (Manifest xs, _) = (Manifest xs, False)
          rebuildManifest (t, reuse) = (t, reuse)

rebuildAllManifestsUnderGather t@(Manifest xs) = (t, True)

但是,请注意:无法保证可观察到的共享,并且可能在两个方向上都不可靠。 GHC 优化器可以完全合法地“重新分享”上述取消分享Manifest 的尝试。我不知道它在实践中会做什么。

此外,此解决方案相当复杂,因此考虑到脆弱性,在调用data-reify 之后 进行显式取消共享传递可能会更好。

【讨论】:

  • Ganesh,感谢您的代码。我也没有尝试过,但在我看来,GHC 可以轻松地使用rebuildManifest t@(Manifest xs) = t 来避免重构该术语。虽然data-reify 可能确实会产生假阴性,但它不会产生假阳性,因此不会破坏任何东西。但是,如果我不取消分享 Manifests,事情会很糟糕。
  • 我记得读过 GHC 从不共享未在源级别共享的内容,因为明确选择避免它以避免问题。现在不是这样了吗?
  • @codeshot 不知道。如果您能找到参考资料,我们中的一个人可以更新答案。
猜你喜欢
  • 2018-07-06
  • 2017-05-17
  • 2018-04-29
  • 1970-01-01
  • 1970-01-01
  • 2018-07-28
  • 2011-04-10
  • 2019-08-22
  • 1970-01-01
相关资源
最近更新 更多