繁体   English   中英

如何在 Haskell 中多次强制评估同一值?

[英]How can one force evaluation of the same value multiple times in Haskell?

我有一个函数bench ,可用于计算评估action所需的时间:

data Benchmark
  = Benchmark POSIXTime POSIXTime
  | BenchmarkN [Benchmark]

bench :: a -> IO Benchmark
bench action
  = do
    start  <- getPOSIXTime
    let !_ = action
    end    <- getPOSIXTime
    return $ Benchmark start end

我试图对所述action的多个基准进行平均,但是随后的action评估几乎立即发生,因为它已经评估过一次:

benchN :: Int -> a -> IO Benchmark
benchN count action
  = BenchmarkN <$> (mapM bench $ replicate count action)

无论如何,是否要强制多次评估action ,从而需要花费全部时间来评估?

回购链接: https : //github.com/wdhg/benchy

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需要很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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM