据我了解,对 IORef 的修改非常快,它们所涉及的只是更新一个 thunk 指针。当然,读者(即想要在其网页上看到值(value)的人)将需要花时间来评估这些重击(如果作者不读回结果,则可能会累积)。
我认为最好开始并行地实际评估 IORef 上的修改 thunk,因为在许多情况下,它们可能必须在某个时刻进行评估(显然,这将破坏无限的数据结构)。
因此,我编写了以下函数,其签名与 atomicModifyIORef
类似:
atomicModifyIORefPar :: (NFData a) => IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORefPar ioref f =
let
g olddata =
let (newdata, result) = f olddata in (newdata, (result, newdata))
in do
(result, newdata) <- atomicModifyIORef ioref g
force newdata `par` return result
这似乎有效( test code here )。我在这里做错了什么吗?或者有更好的方法吗?
<小时/>编辑:第二次尝试
灵感来自Carl's answer below 。我们实际上将force newdata
存储到IORef
中。无论如何,这与 newdata
相同,但显示了我们希望为以后保留force newdata
的运行时,因此它不会对 Spark 进行垃圾收集。
atomicModifyIORefPar :: (NFData a) => IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORefPar ioref f =
let
g olddata =
let
(newdata, result) = f olddata
newdata_forced = force newdata
in
(newdata_forced, (result, newdata_forced))
in do
(result, newdata_forced) <- atomicModifyIORef ioref g
newdata_forced `par` return result
最佳答案
这可能有效,也可能无效,具体取决于 GHC 的版本。 Spark 池与 GC 的交互在整个历史中一直是变化的。在某些版本中,事实上,在 atomicModifyIORefPar
返回后,表达式 force newdata
没有被范围内的任何内容引用,这意味着它很可能在由par
ever 被转换,这意味着 Spark 也将被收集。
其他版本的 GHC 将 Spark Pool 视为 GC 分析的根,但这也存在问题。我不记得当前状态是什么,但我怀疑 Spark 池不算作 GC 根。它引发的问题(当返回的表达式不引用并行计算的表达式时,并行性丢失)比将 Spark 池视为 GC 根(保留不需要的内存)所产生的问题要好一些。
<小时/>编辑 - 第二次尝试回答
出于您给出的原因,这个新的实现看起来是正确的。并行计算的表达式也可以从 GC 根到达。
关于Haskell:尝试并行 `atomicModifyIORef` 实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10203446/