【问题标题】:Haskell: How to benchmark a computation accurately with deepseq/forceHaskell:如何使用 deepseq/force 准确地对计算进行基准测试
【发布时间】:2013-09-04 11:14:02
【问题描述】:

我有一个用 Haskell 编写的 Web 服务器,它分多个步骤计算一些数据。

我想准确测量并显示每个操作需要多长时间。

在懒惰的情况下,有什么好的方法可以做到这一点?


请注意,“基准测试”并不是一个完全正确的术语,因为我只想在生产系统中测量时间,而不是对多次运行进行采样。我知道在这种情况下我可以使用criterion

【问题讨论】:

    标签: haskell benchmarking lazy-evaluation


    【解决方案1】:

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

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

    这是因为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)
    

    正如我们所见,forcemap id l 的情况下产生了大约 13% 的开销。

    【讨论】:

    • 我的chronograph 包可用于在存在懒惰的情况下测量评估时间。不幸的是,force 时间包含在测量中,正如您所指出的,它可能很重要。
    猜你喜欢
    • 1970-01-01
    • 2016-12-25
    • 2013-03-10
    • 2017-12-21
    • 2023-03-13
    • 1970-01-01
    • 1970-01-01
    • 2012-02-02
    • 1970-01-01
    相关资源
    最近更新 更多