我将此代码键入解释器并快速占用内存:
last [1..10^7] `seq` ()
我不明白为什么这需要超过O(1)空间。如果我这样做(这应该是相同的,因为Show强制弱头正常形式,所以seq是多余的?):
last [1..10^7]
......工作正常。
我无法在翻译之外重现这种情况。
这里发生了什么?
以下是一些测试用例: http://hpaste.org/69234
注意事项:
wtf<n>
。ghc --make wtf.hs && ./wtf
。last
可以替换带有严格累加器的sum
或者在列表中找到max元素的函数,并且仍然会发生空间泄漏$!
代替seq
时,我没有看到此行为。()
参数,因为我认为这可能是CAF问题。没有任何改变。Enum
上的函数可能不是问题,因为我可以使用wtf5
及更高版本重现行为,而根本不使用Enum
。Num
,Int
或Integer
可能不是问题,因为我可以在wtf14
和wtf16
中重现没有它们的行为。< / LI>
我尝试使用Peano算法重现问题,将列表和整数排除在等式之外(在10 ^ 9结束时取出),但遇到其他共享/空间泄漏问题我不明白实施*
。
答案 0 :(得分:15)
我们需要查看整数enumFromTo
的实例,最后:
last [] = errorEmptyList "last"
last (x:xs) = last' x xs
where last' y [] = y
last' _ (y:ys) = last' y ys
在GHC.Enum中定义为:
enumFrom x = enumDeltaInteger x 1
enumFromThen x y = enumDeltaInteger x (y-x)
enumFromTo x lim = enumDeltaToInteger x 1 lim
,其中
enumDeltaInteger :: Integer -> Integer -> [Integer]
enumDeltaInteger x d = x `seq` (x : enumDeltaInteger (x+d) d)
-- strict accumulator, so
-- head (drop 1000000 [1 .. ]
-- works
和
enumDeltaToInteger :: Integer -> Integer -> Integer -> [Integer]
enumDeltaToInteger x delta lim
| delta >= 0 = up_list x delta lim
| otherwise = dn_list x delta lim
up_list :: Integer -> Integer -> Integer -> [Integer]
up_list x0 delta lim = go (x0 :: Integer)
where
go x | x > lim = []
| otherwise = x : go (x+delta)
正如预期的那样, last
完全是懒惰的。
对于Integer Enum类,我们有enumFrom
的严格累加器(显式)。在有界的情况下(例如[1..n]
),它会调用enumDeltaToInteger
然后调用up_list
,它会使用工作人员展开列表,直到达到其限制。
但up_list
帮助x
中的go
严格(请参阅与lim
的比较)。
当在GHCi中运行时,没有一个被优化,在返回()
之前,对enumFromTo进行了天真的调用。
let
it_ax6 :: ()
it_ax6 =
case last
@ GHC.Integer.Type.Integer
(GHC.Enum.enumFromTo
@ GHC.Integer.Type.Integer
GHC.Num.$fEnumInteger
(GHC.Integer.smallInteger 1)
(GHC.Real.^
@ GHC.Integer.Type.Integer
@ GHC.Integer.Type.Integer
GHC.Num.$fNumInteger
GHC.Real.$fIntegralInteger
(GHC.Integer.smallInteger 10)
(GHC.Integer.smallInteger 7)))
of _ -> GHC.Unit.()
in
GHC.Base.thenIO
@ ()
@ [()]
(System.IO.print @ () GHC.Show.$fShow() it_ax6)
(GHC.Base.returnIO
@ [()] (GHC.Types.: @ () it_ax6 (GHC.Types.[] @ ())))
那么,为什么我们保留seq
案例中的列表,而不是常规案例?常规案例在constrant空间中很好地运行,依赖于enumFromTo
和Integer
的{{1}}的懒惰。该案例的GHCi核心如下:
last
所以这几乎是相同的,区别在于:
let {
it_aKj :: GHC.Integer.Type.Integer
[LclId,
Unf=Unf{Src=<vanilla>, TopLvl=False, Arity=0, Value=False,
ConLike=False, Cheap=False, Expandable=False,
Guidance=IF_ARGS [] 170 0}]
it_aKj =
GHC.List.last
@ GHC.Integer.Type.Integer
(GHC.Enum.enumFromTo
@ GHC.Integer.Type.Integer
GHC.Num.$fEnumInteger
(GHC.Integer.smallInteger 1)
(GHC.Real.^
@ GHC.Integer.Type.Integer
@ GHC.Integer.Type.Integer
GHC.Num.$fNumInteger
GHC.Real.$fIntegralInteger
(GHC.Integer.smallInteger 10)
(GHC.Integer.smallInteger 7))) } in
GHC.Base.thenIO
@ ()
@ [()]
(System.IO.print
@ GHC.Integer.Type.Integer GHC.Num.$fShowInteger it_aKj)
(GHC.Base.returnIO
@ [()]
(GHC.Types.:
@ ()
(it_aKj
`cast` (UnsafeCo GHC.Integer.Type.Integer ()
:: GHC.Integer.Type.Integer ~ ()))
(GHC.Types.[] @ ())))
版本中seq
被迫last (enumFromTo ..)
。case
。let
版本中,计算该值然后丢弃,产生seq
- 没有看到结果奇怪的是,没有什么神奇之处:
()
使其保留值。
正如我们所见,let x = case last (enumFromTo 1 n) of _ -> ()
的实现在其累加器中是严格的(因为它与up_list
进行比较,并且列表是懒散展开的 - 因此lim
应该能够消耗它在恒定的空间)。手写表达确认了这一点。
执行ghci执行的堆配置文件显示保留整个列表:
它告诉我们至少它不是一串thunk,而是整个列表正在严格建立并保持不变,直到被丢弃。
所以神秘之处在于:什么是ghci中last
的列表参数,而不是ghc?
我怀疑ghci的一些内部(或微妙)细节 - 我认为这是值得的ghci票。
答案 1 :(得分:1)
我认为@n.m是对的。 没有什么东西强迫列表中的值,所以1 + 1 + 1 + 1 + ... thunk最终会杀死空间。
我将进行快速测试。