Haskell:如何使用deepseq/force精确地计算计算

nh2*_*nh2 6 benchmarking haskell lazy-evaluation

我有一个用Haskell编写的Web服务器,它可以通过多个步骤计算一些数据.

我想准确地测量并显示每个动作需要多长时间.

在懒惰的情况下,有什么好办法呢?


请注意,"基准测试"不是一个正确的术语,因为我只想测量生产系统中的时间而不是采样多次运行.我知道,对于那种情况,我可以使用标准.

nh2*_*nh2 4

您可以使用forcefromControl.DeepSeq来全面评估数据结构(从而要求和测量其计算)。

一个问题是强制使用大型数据结构本身需要一些时间!

这是因为 a 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
Run Code Online (Sandbox Code Playgroud)

第一次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 ()
Run Code Online (Sandbox Code Playgroud)

(当然它也适用于 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)
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的,force在这种情况下会产生大约 13% 的开销map id l