Control.Monad foldM意外的内存增长

时间:2016-01-16 19:01:18

标签: haskell

我有以下代码,已被删除,我认为尽可能小,有一些非常奇怪的行为。

代码由两个源文件组成: 一个定义一些数据:

module MyFunction where

data MyFunction =
    MyFunction {
        functionNumber :: Int,
        functionResult :: IO String
        }

makeMyFunction :: Show a => Int -> IO a -> MyFunction
makeMyFunction number result = MyFunction {
    functionNumber = number,
    functionResult = result >>= return . show }

另一个是Main:

module Main (main) where

import System.CPUTime (getCPUTime)
import Data.List (foldl')
import Data.Foldable (foldlM)
import Control.Monad (foldM)
import MyFunction

exampleFunction = do
    --let x = foldl' (\a b -> a `seq` (a + b)) 0 [1..20000000]      -- This works
    --x <- foldlM (\a b -> a `seq` return (a + b)) 0 [1..20000000]  -- This works (*)
    x <- foldM (\a b -> a `seq` return (a + b)) 0 [1..20000000]    -- This doesn't
    print x
    return ()

runFunction fn = do
    result <- functionResult fn
    duration <- getCPUTime
    if result /= "()"
        then putStrLn ""
        else return ()
    putStrLn (show (fromIntegral duration / (10^9)) ++ "ms")
    return fn

main = do
    runFunction (makeMyFunction 123 exampleFunction)
    return ()

上面的代码(使用GHC 7.10.3编译,堆栈1.0.0带有默认标志)内存使用量快速增加(超过1GB),通常需要3.3秒。

如果我对代码进行了更改,例如:

  • 使用问题行的评论替代方案之一
  • 取出runFunction
  • 中的任意一行

内存使用率将保持最低,仅需约1秒钟。

我认为最令我惊讶的一个功能是,将foldM替换为foldlM(据我所知foldM = foldlM)可以解决问题。

同样对我看不到的代码进行更改与问题代码行有任何关系也可以解决问题。例如,删除最后一个putStrLn。

另一个奇怪的是,如果我将MyFunction模块合并到主模块中,虽然它不能解决问题,但它实际上会导致foldlM使用过多的内存来表现为foldM

在它来自的真实代码中,我有大量的exampleFunction,并且有更多的Main代码,而且每隔一段时间我就会遇到这种无法解释的内存使用情况,通常可以通过某种巫术来解决。

我正在寻找行为的解释。如果我知道为什么会发生这种情况,我可以考虑避免它。这可能是编译器问题,或者可能只是我的误解?

(*)我强调了导致foldlM发生相同内存增长的次要问题。

1 个答案:

答案 0 :(得分:3)

以下是来自foldlM(ghc)

Foldable.hs
-- | Monadic fold over the elements of a structure,
-- associating to the left, i.e. from left to right.
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldr f' return xs z0
  where f' x k z = f z x >>= k
来自foldM

Monad.hs

foldM          :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
{-# INLINEABLE foldM #-}
{-# SPECIALISE foldM :: (a -> b -> IO a) -> a -> [b] -> IO a #-}
{-# SPECIALISE foldM :: (a -> b -> Maybe a) -> a -> [b] -> Maybe a #-}
foldM          = foldlM

我将这些定义放在一个单独的模块中测试并使用和不使用INLINEABLE / SPESIALISE行测试执行。无论原因是什么,省略SPECIALIZE指令有助于执行时间和内存使用与foldlM类似。

经过多挖掘,删除线

{-# SPECIALISE foldM :: (a -> b -> IO a) -> a -> [b] -> IO a #-}

受影响最大。