使用 Criterion 对 IO 操作进行基准测试

Benchmarking IO action with Criterion

我想知道我的程序将一个 12.9MB .wav 文件读入内存需要多长时间。将文件读入内存的函数如下所示:

import qualified Data.ByteString        as BS

getSamplesFromFileAsBS :: FilePath -> IO (BS.ByteString)

它将文件名和returns样本作为ByteString。它还对数据执行一些其他有效性检查并忽略 header 信息。我使用 ByteString.hGet.

ByteString 个样本读入内存

如果我现在用一个 12.9MB 的文件对这个函数进行基准测试,使用 Criterion:

bencher :: FilePath -> IO ()
bencher fp = defaultMain [
  bench "Reading all the samples from a file." $ nfIO (getSamplesFromFileAsBS fp)
  ]

我得到以下结果:

benchmarking Reading all the samples from a file.
time                 3.617 ms   (3.520 ms .. 3.730 ms)
                     0.989 R²   (0.981 R² .. 0.994 R²)
mean                 3.760 ms   (3.662 ms .. 3.875 ms)
std dev              354.0 μs   (259.9 μs .. 552.5 μs)
variance introduced by outliers: 62% (severely inflated)

似乎在 3.617 毫秒内将 12.9MB 加载到内存中。这似乎不现实,因为它表明我的 SSD 可以读取 3+GB/s,但事实并非如此。我做错了什么?

我决定通过手动测量时差来尝试另一种(更天真的)方法:

runBenchmarks :: FilePath -> IO ()
runBenchmarks fp = do
  start <- getCurrentTime
  samplesBS <- getSamplesFromFileAsBS fp
  end <- samplesBS `deepseq` getCurrentTime
  print (diffUTCTime end start)

这给了我以下结果:0.023105s。这是现实的,因为这意味着我的 SSD 可以以大约 600MB/s 的速度读取。 Criterion 结果有什么问题?

我通过将输出写入 html 文件来查看 Criterion 基准测试的视觉结果。我可以清楚地看到第一个 运行 花费了大约 0.020 秒,而其余的(缓存后)花费了大约 0.003 秒。

所以我得到这些结果是因为缓存。