我有以下代码,已被剥离,我认为尽可能小,有一些非常奇怪的行为.
代码由两个源文件组成:一个用于定义一些数据:
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 }
Run Code Online (Sandbox Code Playgroud)
另一个是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 ()
Run Code Online (Sandbox Code Playgroud)
上面的代码(使用GHC 7.10.3编译,堆栈1.0.0带有默认标志)内存使用量快速增加(超过1GB),通常需要3.3秒.
如果我对代码进行了更改,例如:
runFunction内存使用量将保持最小,仅需1秒左右.
一个功能,我认为这是最令人惊讶的对我来说是替换foldM用foldlM(这是据我所知foldM = foldlM)解决了这个问题.
此外,对我看不到的代码进行更改与问题代码行有任何关系也可以解决问题.例如,删除最后一个putStrLn.
另一个奇怪的是,如果我将MyFunction模块合并到主模块中,虽然它不能解决问题,但它实际上会导致foldlM表现为foldM使用过多的内存.
在它来自的真实代码中,我有大量exampleFunction的Main代码,并且代码明显增多,而且我经常会遇到函数中这种无法解释的内存使用情况,这通常可以通过某种伏都教来解决.
我正在寻找行为的解释.如果我知道为什么会发生这种情况,我可以考虑避免它.这可能是一个编译器问题,或者可能只是我的误解?
(*)我已经强调了使用foldlM导致相同内存增长的次要问题.
这是foldlM来自Foldable.hs(ghc)
-- | 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
Run Code Online (Sandbox Code Playgroud)
和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
Run Code Online (Sandbox Code Playgroud)
我将这些定义放置到一个单独的模块 Test 中,并测试了带或不带 INLINEABLE / SPESIALISE 行的执行情况。无论原因是什么,省略 SPECIALIZE 指令都会有所帮助,并且执行时间和内存使用情况与使用foldlM 类似。
进一步挖掘后,移除线
{-# SPECIALISE foldM :: (a -> b -> IO a) -> a -> [b] -> IO a #-}
Run Code Online (Sandbox Code Playgroud)
影响最大。