common/util/Util/Timing.hs (85 lines of code) (raw):
-- Copyright (c) Facebook, Inc. and its affiliates.
module Util.Timing
( reportTime
, reportAndShowTime
, showAllocs
, showTime
, timeIt
, timeNF
, timeItNoGC
) where
import Control.DeepSeq (NFData, force)
import Control.Exception (evaluate)
import Control.Monad.IO.Class
import Data.Time.Clock
import Data.Int
import Text.Printf
import GHC.Stats
import GHC.Conc
-- | Runs an 'IO' operation and prints how long it took. Also returns
-- the timing value for use.
reportAndShowTime :: String -> IO a -> IO (Double, Int64, a)
reportAndShowTime name io = do
(t, b, a) <- timeIt io
printf "%s: %s, %s\n" name (showTime t) (showAllocs b)
return (t, b, a)
-- | Runs an 'IO' operation and reports how long it took. Useful for
-- ad-hoc benchmarking.
reportTime :: String -> IO a -> IO a
reportTime name io = do
(_, _, a) <- reportAndShowTime name io
return a
-- | Converts time in seconds to a human friendly string.
showTime :: Double -> String
showTime t = printf "%.2f%s" val unit
where
unit :: String
(val, unit)
| t >= 1 = (t, "s")
| t >= 1e-3 = (t * 1e3, "ms")
| t >= 1e-6 = (t * 1e6, "us")
| otherwise = (t * 1e9, "ns")
-- | Converts a number of bytes to a human-friendsly string.
showAllocs :: Int64 -> String
showAllocs b
| b' > gb = printf "%.2f GB" (b' / gb)
| b' > mb = printf "%.2f MB" (b' / mb)
| b' > kb = printf "%.2f kB" (b' / kb)
| otherwise = printf "%d bytes" (fromIntegral b :: Integer)
where
kb, mb, gb :: Double
kb = 1024
mb = 1024*kb
gb = 1024*mb
b' = fromIntegral b :: Double
-- | Runs an 'IO' action and returns a triple of the time it consumed (in sec),
-- the number of bytes it allocated, and the result it returned.
timeIt :: MonadIO m => m a -> m (Double, Int64, a)
timeIt action = do
t0 <- liftIO getCurrentTime
a0 <- liftIO getAllocationCounter
ret <- action
a1 <- liftIO getAllocationCounter
t1 <- liftIO getCurrentTime
return (realToFrac $ t1 `diffUTCTime` t0, a0 - a1, ret)
-- | Runs an 'IO' action, reduces the result to normal form and returns a pair
-- of the time it consumed and the result it returned.
timeNF :: NFData a => IO a -> IO (Double, Int64, a)
timeNF action = do
t0 <- getCurrentTime
a0 <- getAllocationCounter
ret <- evaluate . force =<< action
a1 <- getAllocationCounter
t1 <- getCurrentTime
return (realToFrac $ t1 `diffUTCTime` t0, a0 - a1, ret)
-- | Records the time to run an action excluding GC time. Probably
-- more expensive than 'timeIt'.
timeItNoGC :: IO a -> IO (Double, Int64, a)
timeItNoGC io = do
t0 <- getRTSStats
a0 <- getAllocationCounter
ret <- io
a1 <- getAllocationCounter
t1 <- getRTSStats
let diff_ns = mutator_elapsed_ns t1 - mutator_elapsed_ns t0
return (fromIntegral diff_ns / 1000000000, a0 - a1, ret)