我有一个用Haskell编写的Web服务器,可以分多步计算一些数据。
我想准确衡量并显示每项操作需要多长时间。
在懒惰的情况下,有什么好办法呢?
请注意,“基准测试”并不是一个正确的术语,因为我只想测量生产系统中的时间而不是采样多次运行。我知道在这种情况下我可以使用criterion。
答案 0 :(得分:4)
您可以使用force
中的Control.DeepSeq
来全面评估数据结构(从而要求并衡量其计算结果)。
一个问题是迫使大型数据结构本身需要一些时间!
这是因为deepseq
(由force
使用)将沿着你的代数数据类型树走,访问每个节点(但不对它做任何事情)。
当您对每个节点执行廉价操作时,例如map (*2) mylist
,并尝试测量所需的时间,这种开销会突然变得很大,从而弄乱您的测量结果。
import Control.DeepSeq
import Control.Exception (evaluate)
import Data.Time (diffUTCTime, getCurrentTime)
-- | Measures how long a computation takes, printing both the time and the
-- overhead of `force` to stdout. So it forces *twice*.
benchmarkForce :: NFData a => String -> IO a -> IO a
benchmarkForce msg action = do
before <- getCurrentTime
-- Force the first time to measure computation + forcing
result <- evaluate . force =<< action
after <- getCurrentTime
-- Force again to see how long forcing itself takes
_ <- evaluate . force $ result
afterAgain <- getCurrentTime
putStrLn $ msg ++ ": " ++ show (diffTimeMs before after) ++ " ms"
++ " (force time: " ++ show (diffTimeMs after afterAgain) ++ " ms)"
return result
where
-- Time difference `t2 - t1` in milliseconds
diffTimeMs t1 t2 = realToFrac (t2 `diffUTCTime` t1) * 1000.0 :: Double
第一次evaluate . force
投放将确保完全评估您的action
及其返回值。
通过对结果进行第二次force
运行,我们可以测量它为第一次遍历添加了多少开销。
这当然是以两次遍历为代价的;能够衡量deepseq
浪费的时间需要你浪费时间两次。
以下是使用它测量一些纯函数的示例:
main :: IO ()
main = do
l <- benchmarkForce "create list" $
return [1..10000000 :: Integer]
_ <- benchmarkForce "double each list element" $
return $ map (*2) l
_ <- benchmarkForce "map id l" $
return $ map id l
return ()
(当然它也适用于IO中的函数。)
输出:
create list: 1091.936 ms (force time: 71.33200000000001 ms)
double each list element: 1416.0569999999998 ms (force time: 96.808 ms)
map id l: 484.493 ms (force time: 67.232 ms)
正如我们所看到的,force
在map id l
案例中产生了大约13%的开销。