一次创建多个标准基准

Creating multiple Criterion Benchmarks at once

这段代码编译运行没有问题:

module Main where

import Criterion.Main

main :: IO ()
main =
  defaultMain
    [env (return $ [1,2])
         (\is ->
            bgroup "group" (benchmarks is))]

timesTwo :: Int -> Int
timesTwo i = 2 * i

benchmarks :: [Int] -> [Benchmark]
benchmarks is = [ bench "foo" $ nf timesTwo (is !! 0)
                , bench "foo" $ nf timesTwo (is !! 1) ]

然而,如果我将 benchmarks 函数更改为

benchmarks :: [Int] -> [Benchmark]
benchmarks is = map (\i -> bench "foo" $ nf timesTwo i) is

它仍然可以编译,但我得到这个运行时错误:

ghci> main
*** Exception: Criterion atttempted to retrieve a non-existent environment!
        Perhaps you forgot to use lazy pattern matching in a function which
        constructs benchmarks from an environment?
        (see the documentation for `env` for details)

我该如何解决这个问题?

如您所见,我的目标是映射从环境中获取的列表,以便将其转换为我的 Benchmarks 列表可以与 Criterion 一起使用。

注意:我最终想使用的元素远不止两个,所以元组不是我想要的。

env很挑剔,也很严格。你不能在这里使用它。在 env 下创建的基准测试的结构不能依赖于环境。也就是说, 正在被基准测试的代码 可以使用该环境,但是 基准测试 本身的组织方式、命名方式等不能用它。这是因为 criterion 有时会传递 _|_ 而不是真实环境,当它只想检查基准的结构而不执行它们时。当您使用 !! 时,基准的组织是手动给出的,即使在 is = _|_:

时也是完整的
benchmarks _|_ = [ bench "foo" $ nf timesTwo _|_ -- _|_ !! n = _|_; nf is not strict
                 , bench "foo" $ nf timesTwo _|_ ] -- "bench"s are still there

但是 map 打破了这个:

benchmarks _|_ = map _etc _|_
               = case _|_ of -- definition of map
                      [] -> []
                      x:xs -> _etc x : map _etc xs
               = _|_ -- benchmark structure gone

你最好的选择就是不使用 env:

main = do is <- _ -- however you calculate is
          defaultMain $ bgroup "group" $ benchmark is

对于不同尺寸的基准测试,我通常会这样做:

module Main (main) where

import Criterion.Main
import System.Random
import Control.Monad

import qualified Data.List
import qualified Data.Sequence

int :: Int -> IO Int
int n = randomRIO (0,n)

benchAtSize :: Int -> Benchmark
benchAtSize n =
    env (replicateM n (int n)) $
    \xs ->
         bgroup (show n)
           [ bench "Data.List"     $ nf Data.List.sort xs
           , bench "Data.Sequence" $ nf (Data.Sequence.sort . Data.Sequence.fromList) xs
           ]

main :: IO ()
main = defaultMain (map benchAtSize [100, 1000, 10000])

env 对于确保在同一个样本上比较两个不同的函数很有用,它并不是为了在 运行 基准之前计算整个数据集而设计的。此外,由于 env 创建的所有数据在对其范围内的任何内容进行基准测试期间都保存在内存中,因此您希望尽可能减少它,以减少基准测试时的开销。