【发布时间】:2016-01-16 19:01:18
【问题描述】:
我有以下代码,它已被剥离,我认为尽可能少,但有一些非常奇怪的行为。
代码由两个源文件组成: 一、定义一些数据:
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 秒。
我认为最令我惊讶的一个功能是用foldlM(据我所知foldM = foldlM)替换foldM 可以解决问题。
还对我看不到的代码进行更改与问题代码行有任何关系也可以解决问题。例如删除最后一个 putStrLn。
另一个奇怪的地方是,如果我将 MyFunction 模块合并到 Main 模块中,虽然它不能解决问题,但它实际上会导致 foldlM 使用过多的内存作为 foldM。
在这个来自的真实代码中,我有大量的exampleFunctions,并且有明显更多的Main 代码,而且我经常遇到这种来自函数的无法解释的内存使用情况,通常可以用某种巫术来解决。
我正在寻找该行为的解释。如果我知道为什么会发生这种情况,我可以考虑避免它。这可能是编译器问题,还是我的误解?
(*) 我强调了导致 foldlM 出现相同内存增长的次要问题。
【问题讨论】:
-
在模块之间移动东西会强烈影响行为这一事实表明,GHC 的内联器也参与其中。最有可能的是,通过内联实现的某些转换要么帮助你,要么伤害你。
-
一个不相关的问题是您在 exampleFunction 中的算术默认为 Integer,这将非常慢。开启
-Wall。 -
@jberryman exampleFunction 仅作为示例 - 显示内存增加。
-
查看使用
ghc --make -fforce-recomp -Wall -O1 -ddump-simpl -dsuppress-all -ddump-inlinings -ddump-rule-firings -ddump-to-file和 gvimdiff 编译 foldM 和 foldlM 版本的 *.dump-simpl 和 *.dump-inlinings 文件。在您的慢版本列表中没有被融合(您可以使用 grep 查找“:”)。如果你喜欢做这种事情,你可以试着弄清楚为什么会发生这种情况,但我认为最好报告一个错误。 -
@pticawr 是的,我想是的;我在你的慢版本中看到的主要区别是中间列表没有融合,这都是库级别的问题:它由重写规则和 INLINE pragma 控制(这可能非常脆弱,而且难以理解)。我想作者想知道
foldlM没有表现出与foldM相同的行为w/r/t 内联和优化。
标签: haskell