【发布时间】:2020-08-23 06:46:25
【问题描述】:
经过几个小时的调试,我意识到一个非常简单的玩具示例效率不高,因为表达式 return $ 1 + x 中缺少 !(感谢 duplode!...但是为什么 ghc 没有优化呢? )。我也意识到了这一点,因为我将它与更快的 Python 代码进行比较,但我不会总是编写 Python 代码来对我的代码进行基准测试......
所以这是我的问题:有没有办法自动检测这些“惰性内存泄漏”,无缘无故减慢程序速度?我在优化 Haskell 代码方面仍然很糟糕,而且很可能忘记!,即使我猜你有经验。
我知道:
-
+RTS -s,但我不确定如何解释它:例如,看到一个简单程序的内存79MB对我来说似乎很大,但也许不是因为它是我当前程序的原因......对于更大的程序,我猜不可能只检测“延迟泄漏”,因为我不知道我的程序应该占用多少内存。 -
cabal v2-run --enable-profiling mysatsolvers -- +RTS -p命令,但似乎启用分析器会破坏 GHC 所做的一些优化,因此很难将这些值用于真正的基准测试。尽管如此,我仍然不清楚如何从该输出中找到泄漏。
您能举例说明一下我如何在像这样的玩具程序中找到“惰性泄漏”吗?
{-# LANGUAGE DerivingVia, FlexibleInstances, ScopedTypeVariables #-}
module Main where
--- It depends on the transformers, containers, and base packages.
--- Optimisation seems to be important or the NoLog case will be way to long.
--- $ ghc -O Main.hs
import qualified Data.Map.Strict as MapStrict
import Data.Functor.Identity
import qualified Control.Monad as CM
import qualified Control.Monad.State.Strict as State
import qualified Data.Time as Time
-- Create a class that allows me to use the function "myTell"
-- that adds a number in the writer (either the LogEntry
-- or StupidLogEntry one)
class Monad m => LogFunctionCalls m where
myTell :: String -> Int -> m ()
---------- Logging disabled ----------
--- (No logging at all gives the same time so I don't put here)
newtype NoLog a = NoLog { unNoLog :: a }
deriving (Functor, Applicative, Monad) via Identity
instance LogFunctionCalls NoLog where
myTell _ _ = pure ()
---------- Logging with Map ----------
-- When logging, associate a number to each name.
newtype LogEntryMap = LogEntryMap (MapStrict.Map String Int)
deriving (Eq, Show)
instance LogFunctionCalls (State.State LogEntryMap) where
myTell namefunction n = State.modify' $
\(LogEntryMap m) ->
LogEntryMap $ MapStrict.insertWith (+) namefunction n m
---------- Logging with Int ----------
-- Don't use any Map to avoid inefficiency of Map
newtype LogEntryInt = LogEntryInt Int
deriving (Eq, Show)
instance LogFunctionCalls (State.State LogEntryInt) where
myTell namefunction n = State.modify' $
\(LogEntryInt m) -> LogEntryInt $! m + n
---------- Function to compute ----------
countNumberCalls :: (LogFunctionCalls m) => Int -> m Int
countNumberCalls 0 = return 0
countNumberCalls n = do
myTell "countNumberCalls" 1
x <- countNumberCalls $! n - 1
return $ 1 + x
main :: IO ()
main = do
let www = 15000000
putStrLn $ "Let's start!"
--- Logging disabled
t0 <- Time.getCurrentTime
let n = unNoLog $ countNumberCalls www
putStrLn $ "Logging disabled: " ++ (show n)
t1 <- Time.getCurrentTime
print (Time.diffUTCTime t1 t0)
-- Logging with Map
let (n, LogEntryMap log) = State.runState (countNumberCalls www) (LogEntryMap MapStrict.empty)
putStrLn $ "Logging with Map: " ++ (show n)
putStrLn $ (show $ log)
t2 <- Time.getCurrentTime
print (Time.diffUTCTime t2 t1)
-- Logging with Int
let (n, LogEntryInt log) = State.runState (countNumberCalls www) (LogEntryInt 0)
putStrLn $ "Logging with Int: " ++ (show n)
putStrLn $ (show $ log)
t3 <- Time.getCurrentTime
print (Time.diffUTCTime t3 t2)
【问题讨论】:
-
请考虑在此处为您的案例开一张 GHC 票。需求分析器在这里无法改进可能是有原因的,但谁知道呢。 GHC 总部可能会喜欢这个独立的例子
标签: haskell optimization memory-leaks lazy-evaluation