惰性数据类型的内存使用情况

时间:2017-02-18 18:38:44

标签: haskell memory vector lazy-evaluation

我编写了一个程序,用于分析文件中的数据并对其执行操作。我的第一个实现使用Data.ByteString来读取文件的内容。然后使用Data.Vector.Unboxed将此内容转换为样本矢量。然后,我对此(未装箱的)样本值向量执行处理和分析。

就像一个实验一样,我想知道如果我使用Haskell的懒惰会发生什么。我决定使用Data.ByteString.Lazy代替Data.ByteStringData.Vector代替Data.Vector.Unboxed进行此简单测试。我希望看到内存使用量有所改善。即使我的程序最终需要知道每个样本的值,我仍然希望内存使用量逐渐增加。当我描述我的节目时,结果让我感到惊讶。

我的原始版本在大约20毫秒内完成,其内存使用情况如下所示: enter image description here 这看起来像我的懒惰行为。样本似乎被加载到内存中,因为我的程序需要它们。

使用Data.VectorData.ByteString给出了以下结果: enter image description here 这看起来与懒惰行为相反。所有样品似乎立即加载到内存中,然后逐个删除。

我怀疑这与我对BoxedUnboxed类型的误解有关,因此我尝试将Data.ByteString.Lazy与`Data.Vector.Unboxed'一起使用。这是结果: enter image description here 我不知道如何解释我在这里看到的内容。

有人能解释我得到的结果吗?

修改 我正在使用hGet从文件中读取,这给了我一个Data.ByteString.Lazy。我通过以下函数将此ByteString转换为Data.Vector Floats:

toVector :: ByteString -> Vector Float
toVector bs =  U.generate (BS.length bs `div` 3) $ \i ->
     myToFloat [BS.index bs (3*i), BS.index bs (3*i+1), BS.index bs (3*i+2)]
  where
    myToFloat :: [Word8] -> Float
    myToFloat words = ...

浮点数以3个字节表示。

其余处理主要包括将高阶函数(例如filtermap等)应用于数据。

EDIT2 我的解析器包含一个函数,该函数从文件中读取所有数据,并在样本矢量中返回此数据(使用之前的toVector函数)。我已经写了两个版本的程序,一个版本Data.ByteString,另一个版本Data.ByteString.Lazy。我已经使用这两个版本来执行一个简单的测试:

main = do
  [file] <- getArgs
  samples <- getSamplesFromFile file
  let slice = V.slice 0 100000 samples
  let filtered = V.filter (>0) slice
  print filtered

严格版本给了我以下内存使用情况: enter image description here 懒惰版本给我以下内存使用情况: enter image description here 这个结果似乎与我期待的完全相反。有人可以解释一下吗? Data.ByteString.Lazy有什么问题?

2 个答案:

答案 0 :(得分:4)

您在惰性字节字符串上使用length。这将需要整个字符串。如果这是输入lazy bytestring的唯一用途,垃圾收集可以使它在恒定空间中工作。但是,您可以在此之后访问该字符串以进行进一步计算,从而强制整个数据在内存中保留。

解决这个问题的方法是完全避免length,并尝试折叠懒惰的字节串(只需一次!),以便流式传输可以完成其工作。

例如,您可以执行类似

的操作
myread :: ByteString -> [Float]
myread bs = case splitAt 3 bs of
   ([x1,x2,x3], end) -> myToFloat x1 x2 x3 : myread end
   -- TODO handle shorter data as well

toVector bs = U.fromList $ myread bs

可能有更好的方法来利用Vector的东西。 U.unfoldr看起来很有希望。

答案 1 :(得分:3)

我们迄今为止的数据不足以重现问题。在这里,我运行了http://sprunge.us/PeIJ的四个版本,将strict更改为lazy,并将boxed设置为unboxed。我正在使用public abstract class BarLineScatterCandleBubbleRenderer extends DataRenderer { // ... lines removed ... // public void set(BarLineScatterCandleBubbleDataProvider chart, IBarLineScatterCandleBubbleDataSet dataSet) { float phaseX = Math.max(0.f, Math.min(1.f, mAnimator.getPhaseX())); float low = chart.getLowestVisibleX(); float high = chart.getHighestVisibleX(); Entry entryFrom = dataSet.getEntryForXValue(low, Float.NaN, DataSet.Rounding.DOWN); //my edits here int indexTo = dataset.getEntryIndex(high, Float.NaN, DataSet.Rounding.UP); List<Entry> values = dataset.getValues(); while (indexTo + 1 < values.size() && values.get(indexTo + 1).getX() == high) { indexTo++; } Entry entryTo = values.get(indexTo); //my edits end here min = entryFrom == null ? 0 : dataSet.getEntryIndex(entryFrom); max = entryTo == null ? 0 : dataSet.getEntryIndex(entryTo); range = (int) ((max - min) * phaseX); } // ... lines removed ... // } 进行编译唯一值得注意的是,ghc -O2 -rtsopts -prof版本中的向量或流中的每个真实(指针)元素都指向一个漂亮的盒装Haskell浮点数,它占用了一个堆空间。一切都基本相同,除了Data.Vector程序,正如预期的那样,这些精心打包的花车顶部有一大堆蓝色。

enter image description here

编辑 如果我只使用Data.Vector

,这就是我得到的

enter image description here