我编写了一个程序,用于分析文件中的数据并对其执行操作。我的第一个实现使用 Data.ByteString 来读取文件的内容。然后使用Data.Vector.Unboxed
将此内容转换为样本向量。然后,我对这个(未装箱的)样本值向量执行处理和分析。
作为一个实验,我想知道如果我利用 Haskell 的惰性会发生什么。我决定使用 Data.ByteString.Lazy
而不是 Data.ByteString
和 Data.Vector
而不是 Data 来进行这个简单的测试.Vector.Unboxed
。我期望看到内存使用情况有所改善。即使我的程序最终需要知道每个样本的值,我仍然期望内存使用量会逐渐增加。当我分析我的程序时,结果令我惊讶。
我的原始版本在大约 20 毫秒内完成,其内存使用情况如下: 在我看来,这似乎是一种懒惰的行为。示例似乎已加载到内存中,因为我的程序需要它们。
使用 Data.Vector
和 Data.ByteString
得到以下结果:
这看起来与懒惰行为相反。所有样本似乎都被一次性加载到内存中,然后一一删除。
我怀疑这与我对 Boxed 和 Unboxed 类型的误解有关,因此我尝试使用 Data.ByteString.Lazy 与“数据.矢量.未装箱”。结果是这样的: 我不知道如何解释我在这里所看到的。
谁能解释一下我得到的结果吗?
编辑
我正在使用 hGet
读取文件,这给了我一个 Data.ByteString.Lazy
。我通过以下函数将此 ByteString 转换为 float 的 Data.Vector
:
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 = ...
float 用 3 个字节表示。
其余的处理主要包括对数据应用高阶函数(例如filter
、map
等)。
编辑2
我的解析器包含一个函数,该函数从文件中读取所有数据并以样本向量返回该数据(使用之前的 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
严格版本给出了以下内存使用情况:
惰性版本给了我以下内存使用情况:
这个结果似乎与我的预期完全相反。有人可以解释一下吗? Data.ByteString.Lazy
有什么问题吗?
最佳答案
您正在惰性字节字符串上使用length
。这将需要整个字符串。如果这是输入惰性字节串的唯一用途,则垃圾收集可以使其在常量空间中工作。但是,您随后访问该字符串以进行进一步计算,从而强制整个数据保留在内存中。
解决这个问题的方法是完全避免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
看起来很有前途。
关于haskell - 惰性数据类型的内存使用情况,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42319163/