输入文件由两行组成,每行包含许多数字
1 2 3 4...
5 6 7 8...
我想处理每一行的数据,如下所示:
doSomething :: [Int] -> [Int] -> Int
doSomething [] y = 0 -- stop execution. I'm concerted in memory behavior only.
doSomething (x:xs) y = doSomething xs y
main = do
inputdata <- getContents
let (x:xs) = lines inputdata
firstLine = map (read ::String->Int) $ words $ x
secondLine = map (read ::String->Int) $ words $ head xs
print $ doSomething firstLine secondLine
当我运行该程序时,堆分析显示如下:
如果我不使用secondLine(xs
),则该程序将以恒定内存运行。列表的每个条目都firstLine
经过处理,然后由GC丢弃。
为什么消耗的内存这么大?我看到分配的内存量约为100MB,但实际输入数据大小为5MB。
是否head xs
强制将整个第一行读入内存,甚至secondLine
根本不使用?这是堆概要分析图内存增加的主要原因吗?
如何处理具有恒定内存的两行,如后面的执行图?
如果3)的答案取决于处理顺序,我该如何先处理第二行,然后处理第一行?
Q1)为什么占用的内存这么大?我看到分配的内存量约为100MB,但实际输入数据大小为5MB。
String
在Haskell中,它是的类型别名[Char]
,因此沿着实际字节,编译器还必须保留构造函数,内存中每个字符的指向装箱字符的指针和指向下一个构造函数的指针,导致内存使用率> 10倍而不是C样式的字符串。更糟糕的是,文本会在内存中多次存储。
Q3)如何处理具有恒定内存的两行,就像后面的执行图一样?
Q4)如果对Q3)的答案取决于处理顺序,我该如何先处理第二行,然后处理第一行?
不,您不能先处理第二行,因为该lines
函数必须评估第一行中的每个字节才能命中'\ n'换行符。
Q2)头xs是否会强制将整个第一行读入内存,甚至根本不使用secondLine?这是堆概要分析图内存增加的主要原因吗?
并不是head
防止第一行被GC。如果您调整其类型签名doSomething
并xs
直接传递给它,则仍然会发生空间泄漏。关键是(未优化)编译器secondLine
在doSomething
最终到达第一个模式之前不会知道没有使用过,因此程序会保留对的引用xs
。顺便说一句,如果使用-O2编译,则程序将以恒定内存运行。
导致程序空间泄漏的原因主要是以下行:
let (x:xs) = lines inputdata
当x
或被xs
丢弃时,此let子句将内联在转储的Core中。仅当稍后两者都被引用时,Core才会表现出奇怪的行为:它构造一个元组,通过模式匹配对其进行销毁,然后将这两个部分再次构造为一个元组,因此secondLine
实际上通过保留对程序的引用就保留了对元组的引用(x, xs)
因此第一行将永远不会被GC。
核心已secondLine
注释掉:
Rec {
doSomething_rjH
doSomething_rjH =
\ ds_d1lv y_alG ->
case ds_d1lv of _ {
[] -> I# 0;
: x_alH xs_alI -> doSomething_rjH xs_alI y_alG
}
end Rec }
main
main =
>>=
$fMonadIO
getContents
(\ inputdata_app ->
print
$fShowInt
(doSomething_rjH
(map
(read $fReadInt)
(words
(case lines inputdata_app of _ {
[] -> case irrefutPatError "a.hs:6:9-32|(x : xs)"# of wild1_00 { };
: x_auf xs_aug -> x_auf
})))
([])))
main
main = runMainIO main
有空间泄漏的核心:
Rec {
doSomething_rjH
doSomething_rjH =
\ ds_d1ol y_alG ->
case ds_d1ol of _ {
[] -> I# 0;
: x_alH xs_alI -> doSomething_rjH xs_alI y_alG
}
end Rec }
main
main =
>>=
$fMonadIO
getContents
(\ inputdata_app ->
-- *** Construct ***
let {
ds_d1op
ds_d1op =
case lines inputdata_app of _ {
[] -> irrefutPatError "a.hs:6:9-30|x : xs"#;
: x_awM xs_awN -> (x_awM, xs_awN)
} } in
-- *** Destruct ***
let {
xs_awN
xs_awN = case ds_d1op of _ { (x_awM, xs1_XwZ) -> xs1_XwZ } } in
let {
x_awM
x_awM = case ds_d1op of _ { (x1_XwZ, xs1_XwU) -> x1_XwZ } } in
-- *** Construct ***
let {
ds1_d1oq
ds1_d1oq = (x_awM, xs_awN) } in
print
$fShowInt
-- *** Destruct ***
(doSomething_rjH
(map
(read $fReadInt)
(words (case ds1_d1oq of _ { (x1_Xx1, xs1_Xx3) -> x1_Xx1 })))
(map
(read $fReadInt)
(words
(head (case ds1_d1oq of _ { (x1_Xx1, xs1_Xx3) -> xs1_Xx3 }))))))
main
main = runMainIO main
用let
子句替换case .. of
子句将修复空间泄漏:
doSomething :: [Int] -> [Int] -> Int
doSomething [] _ = 0 -- stop execution. I'm concerted in memory behavior only.
doSomething (_:xs) y = doSomething xs y
main :: IO ()
main = do
inputdata <- getContents
case lines inputdata of
x:xs -> do
let
firstLine = map (read ::String->Int) $ words x
secondLine = map (read ::String->Int) $ words $ head xs
print $ doSomething firstLine secondLine
转储的核心。这次没有发生“先构建然后销毁”的模式:
Rec {
doSomething_rjG
doSomething_rjG =
\ ds_d1o6 ds1_d1o7 ->
case ds_d1o6 of _ {
[] -> I# 0;
: ds2_d1o8 xs_alG -> doSomething_rjG xs_alG ds1_d1o7
}
end Rec }
main
main =
>>=
$fMonadIO
getContents
(\ inputdata_apn ->
case lines inputdata_apn of _ {
[] -> patError "a.hs:(8,3)-(13,46)|case"#;
: x_asI xs_asJ ->
print
$fShowInt
(doSomething_rjG
(map (read $fReadInt) (words x_asI))
(map (read $fReadInt) (words (head xs_asJ))))
})
main
main = runMainIO main
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句