在可管理大小的数据集上遇到Data.Sequence的内存问题

时间:2014-12-21 07:35:55

标签: haskell memory-management sequence

TL; DR:我正在编写一段生成(长)数字数组的代码。我能够生成这个数组,将其转换为List,然后计算最大值(使用严格的左侧折叠)。但是,当我尝试在计算最大值之前将列表转换为Sequence时,我遇到了内存问题。这对我来说非常违反直觉。

我的问题:为什么会发生这种情况以及将数据转换为Sequence结构的正确方法是什么?

背景 我正在研究一个我选择使用三个步骤(下面)解决的问题。

*注意:我故意保持问题陈述含糊不清,所以这篇文章不作为提示。

无论如何,我建议的方法:

(i)首先,生成一个长整数列表,即每个整数的因子数从1到1亿(不是因子本身,只是因子的数量)

(ii)第二,将此列表转换为序列。

(iii)最后,使用一个有效的滑动窗口最大算法来计算我的答案(此步骤需要出列操作,因此需要一个序列)

(同样,问题的细节并不相关,因为我只是好奇为什么我首先遇到这个特定的问题。)

到目前为止我做了什么?
第1步非常简单 - 请参阅下面的输出(完整代码包含在底部)。我只是使用Unboxed Array和accumArray函数来强制筛选,没什么特别的。注意:我使用相同的算法来解决许多其他类似的问题,所以我有理由相信它会给出正确的答案。

为了显示执行时间/内存使用统计数据,我(不可否认地)选择了计算结果数组中的最大元素 - 这个想法只是使用一个强制构造所有元素的函数。数组,从而确保我们看到exec时间/内存使用的有意义的统计数据。

以下main功能...

main = print $ maximum' $ elems (sieve (10^8))

...导致以下结果(即,它表示,除数最多的数字低于1亿,总共有768个除数):

Linking maxdivSO ...
768 
33.73s user 70.80s system 99% cpu 1:44.62 total

 344,214,504,640 bytes allocated in the heap
  58,471,497,320 bytes copied during GC
     200,062,352 bytes maximum residency (298 sample(s))
       3,413,824 bytes maximum slop
             386 MB total memory in use (0 MB lost due to fragmentation)

 %GC     time      24.7%  (30.5% elapsed)

问题

似乎我们可以完成第一步而不会出汗,因为我已经为我的VirtualBox分配了总共5gb,上面的代码使用了< 400mb(作为参考,我看到程序成功执行并报告使用3gb +总内存)。换句话说,似乎我们已经完成了步骤1,并且有足够的空间。

所以我对以下版本的main函数失败的原因感到有些惊讶。我们尝试执行相同的最大计算,但是在将整数列表转换为Sequence之后。以下代码......

main = print $ maximum' $ fromList $ elems (sieve (10^8))

...导致以下结果:

Linking maxdivSO ...
maxdivSO: out of memory (requested 2097152 bytes)
  39.48s user 76.35s system 99% cpu 1:56.03 total

我的问题:如果我们尝试将列表转换为序列,为什么算法(如当前编写的)内存不足?我怎样才能成功将此列表转换为序列?“

(对于这些类型的问题,我不是顽固地坚持使用蛮力 - 但我强烈怀疑这个特殊问题是由于我无法对评估做出好的判断。)


代码本身:

{-# LANGUAGE NoMonomorphismRestriction #-}

import Data.Word (Word32, Word16)
import Data.Foldable (Foldable, foldl')

import Data.Array.Unboxed (UArray, accumArray, elems)
import Data.Sequence (fromList)

main :: IO ()
main = print $ maximum' $ elems (sieve (10^8))                -- <-- works
--main = print $ maximum' $ fromList $ elems (sieve (10^8))   -- <-- doesn't work

maximum' :: (Foldable t, Ord a, Num a) => t a -> a
maximum' = foldl' (\x acc -> if x > acc then x else acc) 0

sieve :: Int -> UArray Word32 Word16
sieve u' = accumArray (+) 2 (1,u) ( (1,-1) : factors )
  where
    u = fromIntegral u'
    cap = floor $ sqrt (fromIntegral u) :: Word32
    factors = [ (i*d,j) | d <- [2..cap]
                        , i <- [2..(u `quot` d)]
                        , d <= i, let j = if i == d then 1 else 2
              ]

1 个答案:

答案 0 :(得分:1)

我认为这样做的原因是,获取序列的第一个元素需要在内存中构造完整序列(因为序列的内部表示是树)。在列表中,elems懒惰地生成元素。

不是将整个数组转换为序列,为什么不在滑动窗口中制作序列呢?