惰性数据类型的内存使用
Memory usage for lazy datatypes
我编写了一个程序来分析文件中的数据并对其执行操作。我的第一个实现使用 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
与 `Data.Vector.Unboxed' 一起使用。这是结果:
我不知道如何解释我在这里看到的东西。
谁能解释一下我得到的结果?
编辑
我正在使用 hGet
从文件中读取,这给了我一个 Data.ByteString.Lazy
。我通过以下函数将此 ByteString 转换为 Floats 的 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 = ...
浮点数用 3 个字节表示。
其余的处理主要包括对数据应用高阶函数(例如 filter
、map
等)。
EDIT2
我的解析器包含一个函数,该函数从一个文件中读取所有数据,并 returns 样本向量中的这些数据(使用之前的 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
。这将需要整个字符串。如果那是输入惰性字节串的唯一用途,垃圾收集可以使其在常量 space 中工作。但是,您之后访问该字符串以进行进一步计算,从而迫使整个数据保留在内存中。
解决这个问题的方法是完全避免 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
看起来很有希望。
我们目前拥有的数据不足以重现该问题。这里我 运行 四个版本 http://sprunge.us/PeIJ 将 strict 更改为 lazy,将 boxed 更改为 unboxed。我正在编译 ghc -O2 -rtsopts -prof
唯一值得一提的区别是 Data.Vector
版本中向量或流中的每个真实(指针)元素都指向自身外部一个漂亮的盒装 Haskell 浮点数占了一堆space。一切都基本相同,除了 Data.Vector
程序,正如预期的那样,在顶部有一大堆蓝色用于这些精心包装的花车。
编辑
这是我使用 ghc -prof -rtsopts
得到的结果
我编写了一个程序来分析文件中的数据并对其执行操作。我的第一个实现使用 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
与 `Data.Vector.Unboxed' 一起使用。这是结果:
谁能解释一下我得到的结果?
编辑
我正在使用 hGet
从文件中读取,这给了我一个 Data.ByteString.Lazy
。我通过以下函数将此 ByteString 转换为 Floats 的 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 = ...
浮点数用 3 个字节表示。
其余的处理主要包括对数据应用高阶函数(例如 filter
、map
等)。
EDIT2
我的解析器包含一个函数,该函数从一个文件中读取所有数据,并 returns 样本向量中的这些数据(使用之前的 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
。这将需要整个字符串。如果那是输入惰性字节串的唯一用途,垃圾收集可以使其在常量 space 中工作。但是,您之后访问该字符串以进行进一步计算,从而迫使整个数据保留在内存中。
解决这个问题的方法是完全避免 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
看起来很有希望。
我们目前拥有的数据不足以重现该问题。这里我 运行 四个版本 http://sprunge.us/PeIJ 将 strict 更改为 lazy,将 boxed 更改为 unboxed。我正在编译 ghc -O2 -rtsopts -prof
唯一值得一提的区别是 Data.Vector
版本中向量或流中的每个真实(指针)元素都指向自身外部一个漂亮的盒装 Haskell 浮点数占了一堆space。一切都基本相同,除了 Data.Vector
程序,正如预期的那样,在顶部有一大堆蓝色用于这些精心包装的花车。
编辑
这是我使用 ghc -prof -rtsopts