Performance 为什么这个代码不是常量空间?
我目前正在学习Haskell(作为一名程序员,这是我第一次尝试函数式语言) 我想写一个函数来扫描一个列表并返回该列表的最小和最大元素。前奏曲功能Performance 为什么这个代码不是常量空间?,performance,haskell,Performance,Haskell,我目前正在学习Haskell(作为一名程序员,这是我第一次尝试函数式语言) 我想写一个函数来扫描一个列表并返回该列表的最小和最大元素。前奏曲功能最小值和最大值的作用,但两者同时进行。我想出了以下代码: import Data.List -- Declaration of rand minMax :: [Int] -> Maybe (Int, Int) minMax [] = Nothing minMax (x:xs) = Just (foldl' f (x, x) xs)
最小值
和最大值
的作用,但两者同时进行。我想出了以下代码:
import Data.List
-- Declaration of rand
minMax :: [Int] -> Maybe (Int, Int)
minMax [] = Nothing
minMax (x:xs) = Just (foldl' f (x, x) xs)
where
f (a, b) c = (if c < a then c else a, if c > b then c else b)
编译并运行所有这些与分析,它告诉我它使用超过200 MB的内存,所以它肯定不是一个常数空间函数(我希望它是)
问题是为什么以及我应该改变什么来修复它。据我所知,foldl'
从左到右折叠列表(与生成列表的方式相同),并且不懒惰,因此我不明白为什么内存使用率如此之高。我敢肯定是minMax
函数不正确,因为只需使用
main = print $ take 1000000 $ rand 7666532
给我1MB的使用量,这是我理解和期望的。注意,
foldl'
强制累加器使用弱头标准形式。由于累加器是一个元组,因此它不会强制计算元组的两个元素
如果显式强制这两个元素,则会得到一个常量空间函数:
main = print $ minMax $ take 1000000 $ rand 7666532
f (a, b) c = a `seq` b `seq` (if c < a then c else a, if c > b then c else b)
因此,代码将变成:
minMax :: [Int] -> Maybe (Pair Int Int)
minMax [] = Nothing
minMax (x:xs) = Just (foldl' f (Pair x x) xs)
where
f (Pair a b) c = Pair (if c < a then c else a) (if c > b then c else b)
minMax::[Int]->可能(成对Int)
minMax[]=无
minMax(x:xs)=刚好(foldl'f(对x x)xs)
哪里
f(对AB)c=对(如果cb则c else b)
这完全避免了thunk。在
foldl'
中使用的seq
函数本质上强制将其第一个参数计算为WHNF(弱头范式)
如前所述,一旦您每次调用构造函数的应用程序,WHNF评估就会停止<因此,代码>(a,b)始终在WHNF中,即使a
和b
是重击,因为在到达a
和b
之前,您正在点击元组构造函数(,)
因此,尽管使用了foldl'
,该空间还是会泄漏:
foldl' (\ (a, b) x -> (a + x, b + x)) (0, 1) [1..1000000]
但这并不是:
foldl' (\ (a, b) x -> a `seq` b `seq` (a + x, b + x)) (0, 1) [1..10000000]
有时使用-XBangPatterns
扩展来编写以下代码也很方便:
foldl' (\ (!a, !b) x -> (a + x, b + x)) (0, 1) [1..10000000]
请编辑您的问题并添加完整的代码,包括
rand
的定义。在这里,使用严格的左折叠不会强制计算对的元素,因为累加器已经处于弱头标准形式,元组是惰性的。请使用seq
添加代码,或者定义严格的对数据类型:数据对a b=pair!A.b
并用它代替普通的(a,b)
。我想提到哪些摘要有效地结合了这样的折叠,事实上给出的一个例子是:L.fold((,)L.minimum L.maximum)[1..10000000]
必须:确保您正在使用优化swait进行编译。。。但你仍然在那里储存着thunks。你不是吗?@Jubobs啊,是的。累加器作为(如果..,如果..)
传递。但是,它是一个恒定的空间(每次调用f
时,thunk都会被计算,因此它们的大小是恒定的。一旦您确保函数足够严格,GHC几乎肯定会避免创建thunk,只要您在编译时启用了优化(-O
或-O2
).很好的链接,让我对引擎盖下发生的事情有了更好的了解。现在我很难决定接受哪个答案…我希望我能同时做到这两个。
foldl' (\ (!a, !b) x -> (a + x, b + x)) (0, 1) [1..10000000]