【问题标题】:Memory usage of a recursive array in HaskellHaskell中递归数组的内存使用情况
【发布时间】:2018-12-09 13:09:06
【问题描述】:

我有一个动态编程算法,我发现我的 Haskell 实现非常令人满意,因为它允许我递归地定义主数组,如下所示:

fill :: Int -> Int -> Entry
fill 0 0 = Entry Origin 0.0
fill 0 j = ...
fill i 0 = ...
fill i j = f m i j

m :: Array (Int, Int) Entry
m = listArray dims [fill i j | (i, j) <- range dims]

其中f 是一个中等复杂的函数,它引用主数组mEntry 类型只是一个带有小注解的Double

数据本身非常大,m 最终有大约 1 亿个条目。该算法仍然非常快,但在此过程中使用了约 25GB 的 RAM。从阅读中我了解到这是因为它保留了数组条目的完整计算而不是最终值,但是如果我切换到未装箱的数组,我无法像上面的示例那样递归地定义它们。

有没有办法吃蛋糕(低内存占用)并吃掉它(递归定义)?

【问题讨论】:

  • 根据你的数据,大概消耗250B/entry。也许您可以尝试通过使您的 Entry 类型更严格来改进它,例如通过添加严格性注释和/或解包。您无法避免 Entry 本身的装箱并使用未装箱的数组,但您可能可以从 Entry 的组件中删除装箱。该类型是如何定义的?
  • 您确定要使用整个矩阵进行计算,还是会有一些未评估的条目?很多?
  • 暂时使用 25GB 可以吗?在程序启动时的预计算阶段,甚至可能之前——只要算法本身在运行期间使用较少?
  • 感谢大家的回复。 @chi data Entry = Entry Source Doubledata Source = Origin | Left | Right | Top | Bottom。超级简单。我相信拆箱 Double 部分可能会有所帮助,因为这是递归定义的部分。 @luqui 我估计实际上可能使用了一半的条目,并且内存分析支持这一点。 @DanielWagner 我很好奇你的想法。原则上,任何阶段的 25 GB 都有点重,因为我希望最终在较小的机器上运行它,但我肯定在听。
  • 我肯定会尝试data Entry = Entry {-# UNPACK #-} !Source {-# UNPACK #-} !Double 这样的基本类型。不要忘记使用 -O 开启优化(并避免使用 GHCi 来衡量性能)

标签: haskell memory dynamic-programming


【解决方案1】:

最后,我用未装箱的可变数组得到了合理的结果,如下所示:

import Data.Array.ST as UM
import Control.Monad (forM_)

mbounds = ((0,0), (..., ...))
m = UM.runSTUArray $ do
    arr <- UM.newArray_ mbounds
    let set = UM.writeArray arr
    let get = UM.readArray arr
    let fill (0, 0) = do set (0, 0) 0.0
        fill (i, 0) = do top <- get (i-1, 0)
                         set (i, 0) (f top)
        fill (0, j) = do ...
        fill (i, j) = do ...
    forM_ (UM.range mbounds) fill
    return arr

仍然比我想要的更多的 RAM 被占用(~4GB),但这是一个巨大的改进,我怀疑在我重新运行分析器之后,我会想办法进一步减少它。

【讨论】:

    猜你喜欢
    • 2012-11-26
    • 1970-01-01
    • 1970-01-01
    • 2010-10-13
    • 2020-12-19
    • 2019-08-12
    • 2013-08-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多