Техника, которую использует criterion
, состоит в том, чтобы скомпилировать функцию whnf'
в ее собственном модуле без встраивания и специального -fno-full-laziness
флага оптимизации, что-то вроде:
-- WHNF.hs
{-# OPTIONS_GHC -fno-full-laziness #-}
module WHNF (whnf') where
whnf' :: (a -> b) -> a -> (Int -> IO ())
whnf' f x = go
where
go n | n <= 0 = return ()
| otherwise = f x `seq` go (n-1)
{-# NOINLINE whnf' #-}
Здесь вычисление представляется в двух частях - как функция и аргумент для вызова. Функция whnf'
превращает ее в эталонную функцию Int -> IO ()
, которая берет счетчик репликации и будет безопасно перезапускать вычисления (в частности, приводя их к нормальной форме со слабой головой) указанное количество раз.
Обратите внимание, что счетчик репликации здесь не для генерации нескольких отдельных таймингов. Скорее, он используется для увеличения времени, когда бенчмаркинг действительно быстрых вычислений, так что временные затраты не влияют на эталонный тест. Для медленных вычислений вы можете использовать счетчик 1.
В вашем основном модуле сравнительного анализа вам, как правило, также необходимо представить выражение, которое нужно сравнить с использованием тех же двух частей, функция и аргумент, чтобы вызвать его. Хотя это и не обязательно, может быть удобно ввести тип данных для этого, включая шкалу подсчета репликации:
data Benchmarkable a b = Benchmarkable (a -> b) a Int
, а затем вы можете сравнить ее один раз с:
data Benchmark
= Benchmark POSIXTime POSIXTime
| BenchmarkN [Benchmark]
deriving (Show)
bench :: Benchmarkable a b -> IO Benchmark
bench (Benchmarkable f a n) = do
start <- getPOSIXTime
() <- whnf' f a n
end <- getPOSIXTime
return $ Benchmark start end
или несколько раз:
benchN :: Int -> Benchmarkable a b -> IO Benchmark
benchN count b = BenchmarkN <$> replicateM count (bench b)
Если у вас медленная реализация Фибоначчи:
slowFib :: Integer -> Integer
slowFib 0 = 0
slowFib 1 = 1
slowFib n = slowFib (n-1) + slowFib (n-2)
, где slowFib 35
занимает значительную долю секунды для запуска, вы можете попробовать :
main = print =<< benchN 10 (Benchmarkable slowFib 35 1)
и, кажется, работает нормально, выводит:
BenchmarkN [Benchmark 1586018307.738716168s 1586018308.179642319s,
Benchmark 1586018308.179642466s 1586018308.618854568s,
Benchmark 1586018308.618854653s 1586018309.057612242s,
Benchmark 1586018309.057612287s 1586018309.496228626s,
Benchmark 1586018309.496228714s 1586018309.934910649s,
Benchmark 1586018309.934910697s 1586018310.373258208s,
Benchmark 1586018310.373258295s 1586018310.811727495s,
Benchmark 1586018310.811727542s 1586018311.250130875s,
Benchmark 1586018311.250131005s 1586018311.689046116s,
Benchmark 1586018311.689046207s 1586018312.127901112s]
Полный код для модуля WHNF:
-- WHNF.hs
{-# OPTIONS_GHC -fno-full-laziness #-}
module WHNF (whnf') where
whnf' :: (a -> b) -> a -> (Int -> IO ())
whnf' f x = go
where
go n | n <= 0 = return ()
| otherwise = f x `seq` go (n-1)
{-# NOINLINE whnf' #-}
и сам тест в отдельный модуль:
-- Benchmark.hs
{-# OPTIONS_GHC -O2 #-}
import WHNF
import Data.Time.Clock.POSIX
import Control.Monad
data Benchmarkable a b = Benchmarkable (a -> b) a Int
data Benchmark
= Benchmark POSIXTime POSIXTime
| BenchmarkN [Benchmark]
deriving (Show)
bench :: Benchmarkable a b -> IO Benchmark
bench (Benchmarkable f a n) = do
start <- getPOSIXTime
() <- whnf' f a n
end <- getPOSIXTime
return $ Benchmark start end
benchN :: Int -> Benchmarkable a b -> IO Benchmark
benchN count b = BenchmarkN <$> replicateM count (bench b)
slowFib :: Integer -> Integer
slowFib 0 = 0
slowFib 1 = 1
slowFib n = slowFib (n-1) + slowFib (n-2)
main :: IO ()
main = print =<< benchN 10 (Benchmarkable slowFib 35 1)