如何避免Haskell空间泄漏?

时间:2017-12-30 13:51:01

标签: haskell memory-leaks

上下文

我不久前开始通过解决今年的代码问题来解决Haskell问题。

在解决Part 2 of Day 17时,我遇到了令人讨厌的内存泄漏(空间泄漏) - 我想。

(以下是完整的README,包括第2部分,只有在解决第1部分后才能访问它。)

我的解决方案有效且运行正常,但只有一个脏的小黑客,这迫使Haskell不时地评估中间计算。

我在每5000次迭代后使用traceShow将中间状态打印到控制台(请参阅此处the actual code)。这样程序在合理的时间内完成,并且不会占用太多内存。

问题:如果我删除它(根本不打印中间状态,只打印最后一个状态),程序将占用所有可用内存。 :(

我开始使用iterate,然后我读到使用它可以导致像我注意到的东西。我已经取代了它。没有。尝试了不同的折叠(foldlfoldl'等)。没有。我不确定在这一点上是什么导致这种情况,尽管我的猜测是在某些时候还有一些不太明显的懒惰评估正在进行中。

我的问题:我怎么能避免这个?在我的情况下是什么导致这种情况?

感谢您的时间和见解。哦,我很确定这个问题有更短,更甜的解决方案,但目前我只对导致内存泄漏的原因感兴趣。

测试

我已经隔离了我发现此错误的代码部分。

type Count = Int
type StepSize = Int
type Value = Int
type Position = Int
type Size = Int

data Buffer = Buffer Size Position (Maybe Value)
  deriving Show

data Spinlock = Spinlock StepSize !Position !Value Buffer
  deriving Show

updateBuffer :: Position -> Value -> Buffer -> Buffer
updateBuffer position value (Buffer size trackedPosition trackedValue)
  | position == trackedPosition = Buffer nextSize trackedPosition (Just value)
  | otherwise = Buffer nextSize trackedPosition trackedValue
  where nextSize = size + 1

stepSpinlock :: Count -> Spinlock -> Spinlock
stepSpinlock count spinlock@(Spinlock stepSize position value buffer)
  | count == 0 = spinlock
  | otherwise = stepSpinlock nextCount newSpinlock
  where (Buffer size _ _) = buffer
        nextCount = count - 1
        nextPosition = ((position + stepSize) `mod` size) + 1
        nextValue = value + 1
        newBuffer = updateBuffer nextPosition nextValue buffer
        newSpinlock = Spinlock stepSize nextPosition nextValue newBuffer

main = do
  let stepSize = 371
      buffer = Buffer 1 0 Nothing
      spinlock = Spinlock stepSize 0 0 buffer
      (Spinlock _ _ _ (Buffer _ _ (Just value))) = stepSpinlock (50000000 - 1) spinlock
  print $ value 

我使用stacklts-10.1),GHC 8.2.2运行此功能。

运行它会占用我所有的内存,但在一段时间后无法分配内存错误就会失败。

如果我更换

  | otherwise = stepSpinlock nextCount newSpinlock

用这个

  | otherwise = stepSpinlock nextCount $ if count `mod` 5000 == 0
                                         then traceShow newSpinlock newSpinlock
                                         else newSpinlock 

它在合理的时间内运行。 然后使用stack ghc Part2.hs重新编译,然后再次运行./Part2 < input.txt

1 个答案:

答案 0 :(得分:1)

以下作品:

{-# language BangPatterns #-}

...

stepSpinlock :: Count -> Spinlock -> Spinlock
stepSpinlock count spinlock@(Spinlock !stepSize !position !value !buffer)
...

在每次迭代中,您更新valuebuffer而不对它们做任何事情,因此thunk积累。或者,我建议只使用{-# language Strict #-}。我还注意到在使用CircularBuffer的程序运行期间根本没有使用input.txt