【问题标题】:haskell running out of memory with finite lists有限列表的haskell内存不足
【发布时间】:2020-05-18 17:46:00
【问题描述】:

我在尝试运行诸如此类的中等输入时内存不足:

variation_models 15 25

同时为 ncars 运行更高的数字似乎在速度和内存使用方面产生了巨大差异。

预计会放缓(有更多的东西要比较),但内存使用量的指数增长对我来说没有意义

import Control.Monad

orderedq f [] = True
orderedq f (x:[]) = True
orderedq f (x:y:zs) = f x y && orderedq f (y:zs)

num_orderedq = orderedq (<=)

adds_up_to n xs = n == sum xs

both_conditions f g xs = f xs && g xs

variation_models ncars nlocations =
  filter (both_conditions (adds_up_to nlocations) num_orderedq) $ replicateM ncars [1..nlocations-ncars+1]

是什么导致了内存使用量的巨大差异? replicateM?

【问题讨论】:

  • 我不认为是 replicateM 引起了问题,因为 take 10 $ replicateM 15 [1..1000000] 工作正常
  • 我认为是。试试length $ replicateM 15 [1..11]
  • 这很粗略,但我用过滤器丢弃了大部分结果。我预计丢弃的结果可能是 gc'd
  • length 会丢弃所有结果,但如果您真的想被说服,请尝试使用filter (const False) 代替length

标签: haskell memory


【解决方案1】:

我认为您在其他地方已经看到,您的特定问题(创建总和为给定数字的整数的有序列表)可以使用替代算法更好地解决,而不是过滤大量整数列表。

但是,回到你原来的问题,可以构造一个等价的:

replicateM p [1..n]

以指数时间运行(当然)但空间恒定。

问题是这个表达式或多或少等价于递归:

badPower 0 _ = pure []
badPower p n = [x:xs | x <- [1..n], xs <- badPower (p-1) n]

因此,在列表推导中,对于每个选定的x,整个列表badPower (p-1) n 需要从头开始重新生成。 GHC 明智地决定保留badPower (p-1) n,这样就不需要每次都重新计算。因此,badPower p n 调用需要将整个badPower (p-1) n 列表保存在内存中,这已经考虑了n^(p-1) 元素和指数内存使用,即使不考虑badPower (p-2) n 等。

如果你只是翻转隐式循环的顺序:

goodPower 0 _ = pure []
goodPower p n = [x:xs | xs <- goodPower (p-1) n, x <- [1..n]]

这解决了问题。即使列表goodPower (p-1) n 是“大”的,我们还是取它的第一个元素,对x 的每个值使用它n 次,然后可以丢弃它并移至下一个元素。因此,goodPower (p-1) n 可以在使用时进行垃圾回收。

注意goodPower 生成元素的顺序与badPower 不同,列表的第一个坐标变化最快,而不是最后一个。 (如果这很重要,您可以map reverse $ goodPower ...。虽然reverse 是“慢”,但它仅适用于此处的短名单。)

无论如何,以下程序(实际上)永远运行,但在恒定空间中运行:

power :: Int -> [a] -> [[a]]
power 0 _ = [[]]
power p lst = [x:xs | xs <- power (p-1) lst, x <- lst ]

main = do
  print $ length (power 15 [1..11])

【讨论】:

  • 针对我的技能水平的全面且易于理解的答案。这是我在本网站或亲自收到的最高质量的答案之一。
  • 你说得对,它运行了很长时间,但我对这个函数的结果使用了过滤器。
  • @K.A.Buhr - 非常好!有什么东西可以阻止你的修复被应用到官方的 Haskell 库中吗?
  • @jpmarinier,哪种方式更好取决于Applicative。例如,将replicateM 视为IO。也许我们需要多个版本的replicateM?出于某些目的,使用乘法乘法的版本也可能很有趣。
  • @dfeuer , k-a-buhr: 那么如果goodPower p = foldr (\x xs -&gt; liftA2 (flip (:)) xs x) (pure []) . replicate pfoldl (liftA2 (\xs x -&gt; liftA2 (flip (:)) xs x)) (pure []) . replicate p 会以正确的顺序应用效果,并且在恒定空间中运行吗?
【解决方案2】:
replicateM :: Applicative m => Int -> m a -> m [a] 

当 'm' 为 [] 时,monad join 实现将使 replicateM 从列表元素中构建 n 个元素的所有排列。这种排列的数量写成 P(n,k),等于 n!/(n-k)!。这就是指数级增长的来源。

【讨论】:

  • 但是他们不是一次构建一个,因为他们被要求,测试(大多数被过滤器拒绝),然后是 gc 的候选者?
  • 我不认为 replicateM 可以计算没有尾的头部,换句话说,replicateM 很可能需要递归直到修复点才能返回任何内容。所以它需要建立每一个元素,从尾部到头部,而不是反过来。看看实现应该会给我们启发。
  • 代码在这里hackage.haskell.org/package/base-4.14.0.0/docs/src/… 我对haskell 很陌生,所以重新编写这对我来说相当粗糙。我还不明白liftA2的用法,把我传入的数组当作函数f&lt;=操作符,或者pure的用法
  • 试试&gt; head $ replicateM 15 [1..11][1,1,1,1,1,1,1,1,1,1,1,1,1,1,1][1..11] 的排列吗? :) 顺便说一句,它确实会立即返回。 @SultanLegend 他们确实一次建造一个,但是如果您要求全部 11^15 个,即使将它们全部通过 const False 过滤器,您仍然需要 all 它们。一次一个。回复:实施,replicateM 3 [1..11] == sequence $ replicate 3 [1..11] == do { a &lt;- [1..11]; b &lt;- [1..11]; c &lt;- [1..11]; return [a,b,c] } == [[a,b,c] | a &lt;- [1..11], b &lt;- [1..11], c &lt;- [1..11]].
  • 您忘记了第三条规则:列表中恰好有 k 个数字(对吗?)。高效解决这类事情的关键是一个一个地生成结果,而不用过滤。为此,我们通常需要在选择一个元素到下一个元素之间保持某种状态,在我们进行时保持要产生的结果的有效性,避免产生无效的结果。参见例如this 回答我的类似问题,或 this entry 等。
猜你喜欢
  • 2014-03-24
  • 2016-05-05
  • 1970-01-01
  • 2012-07-03
  • 2011-12-25
  • 2016-08-13
  • 2016-05-22
  • 2013-01-10
  • 2021-06-05
相关资源
最近更新 更多