尝试对 haskell 进行基准测试时找不到模块“Criterion.Main”

Could not find module ‘Criterion.Main’ when trying to benchmark haskell

我无法使 Criterion 正常工作。我遵循教程 here,通过执行以下操作安装 Criterion。

cabal update
cabal install -j --disable-tests criterion

当我尝试 ghc -O Fibber.hsghc -O --make Fibber 时,我收到错误消息

[1 of 1] Compiling Main             ( Fibber.hs, Fibber.o )

Fibber.hs:1:1: error:
    Could not find module ‘Criterion.Main’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
  |
1 | import Criterion.Main
  |

我希望测试和诊断是 运行 并输出到控制台和一个 HTML 文件。 尝试使用堆栈会导致相同的错误。我之前用自己的模块解决了这个问题,方法是将它们包含在我的 .cabal 文件中,就像这样

library
  Exposed-Modules:
    Geometry.Sphere
    Geometry.Cuboid
    Geometry.Cube

但是在这种情况下这样做并不能解决问题。我也试过 this SO solution,但没有用。我收到基本上相同的错误:


/Users/me/Documents/projects/haskell/performance/criterion-1.5.13.0/Criterion/Fibber.hs:3:1: error:
    Could not find module ‘CriterionMain’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
  |
3 | import CriterionMain
  | ^^^^^^^^^^^^^^^^^^^^
Received ExitFailure 1 when running
Raw command: /Users/me/.stack/programs/aarch64-osx/ghc-9.2.2/bin/ghc-9.2.2 -i -i/Users/me/Documents/projects/haskell/performance/criterion-1.5.13.0/Criterion/ -hide-all-packages -fdiagnostics-color=always -packagebase -O2 /Users/me/Documents/projects/haskell/performance/criterion-1.5.13.0/Criterion/Fibber.hs
Run from: /Users/me/Documents/projects/haskell/performance/criterion-1.5.13.0/Criterion/
Standard output:

[1 of 1] Compiling Main             ( /Users/me/Documents/projects/haskell/performance/criterion-1.5.13.0/Criterion/Fibber.hs, /Users/me/Documents/projects/haskell/performance/criterion-1.5.13.0/Criterion/Fibber.o )

这是我的 .cabal 文件。

name:           criterion
version:        1.5.13.0
synopsis:       Robust, reliable performance measurement and analysis
license:        BSD3
license-file:   LICENSE
author:         Bryan O'Sullivan <bos@serpentine.com>
maintainer:     Ryan Scott <ryan.gl.scott@gmail.com>
copyright:      2009-2016 Bryan O'Sullivan and others
category:       Development, Performance, Testing, Benchmarking
homepage:       http://www.serpentine.com/criterion
bug-reports:    https://github.com/haskell/criterion/issues
build-type:     Simple
cabal-version:  >= 1.10
extra-source-files:
  README.markdown
  changelog.md
  examples/LICENSE
  examples/*.cabal
  examples/*.hs
tested-with:
  GHC==7.4.2,
  GHC==7.6.3,
  GHC==7.8.4,
  GHC==7.10.3,
  GHC==8.0.2,
  GHC==8.2.2,
  GHC==8.4.4,
  GHC==8.6.5,
  GHC==8.8.4,
  GHC==8.10.7,
  GHC==9.0.2,
  GHC==9.2.1

data-files:
  templates/*.css
  templates/*.tpl
  templates/*.js

description:
  This library provides a powerful but simple way to measure software
  performance.  It provides both a framework for executing and
  analysing benchmarks and a set of driver functions that makes it
  easy to build and run benchmarks, and to analyse their results.
  .
  The fastest way to get started is to read the
  <http://www.serpentine.com/criterion/tutorial.html online tutorial>,
  followed by the documentation and examples in the "Criterion.Main"
  module.
  .
  For examples of the kinds of reports that criterion generates, see
  <http://www.serpentine.com/criterion the home page>.

flag fast
  description: compile without optimizations
  default: False
  manual: True

flag embed-data-files
  description: Embed the data files in the binary for a relocatable executable.
               (Warning: This will increase the executable size significantly.)
  default: False
  manual: True

library
  exposed-modules:
    Criterion
    Criterion.Analysis
    Criterion.IO
    Criterion.IO.Printf
    Criterion.Internal
    Criterion.Main
    Criterion.Main.Options
    Criterion.Monad
    Criterion.Report
    Criterion.Types

  other-modules:
    Criterion.Main.Options.Internal
    Criterion.Monad.Internal

  other-modules:
    Paths_criterion

  build-depends:
    -- TODO: Eventually, we should bump the lower version bounds to >=2 so that
    -- we can remove some CPP in Criterion.Report. See #247.
    aeson >= 1 && < 2.1,
    ansi-wl-pprint >= 0.6.7.2,
    base >= 4.5 && < 5,
    base-compat-batteries >= 0.10 && < 0.13,
    binary >= 0.5.1.0,
    binary-orphans >= 1.0.1 && < 1.1,
    bytestring >= 0.9 && < 1.0,
    cassava >= 0.3.0.0,
    code-page,
    containers,
    criterion-measurement >= 0.1.1.0 && < 0.2,
    deepseq >= 1.1.0.0,
    directory,
    exceptions >= 0.8.2 && < 0.11,
    filepath,
    Glob >= 0.7.2,
    microstache >= 1.0.1 && < 1.1,
    js-chart >= 2.9.4 && < 3,
    mtl >= 2,
    mwc-random >= 0.8.0.3,
    -- TODO: Depend on optparse-applicative-0.17 as the minimum (see #258)
    optparse-applicative >= 0.13 && < 0.18,
    parsec >= 3.1.0,
    statistics >= 0.14 && < 0.16,
    text >= 0.11,
    time,
    transformers,
    transformers-compat >= 0.6.4,
    vector >= 0.7.1,
    vector-algorithms >= 0.4
  if impl(ghc < 7.6)
    build-depends:
      ghc-prim
  if !impl(ghc >= 8.0)
    build-depends:
      fail == 4.9.*,
      semigroups

  default-language: Haskell2010
  ghc-options: -Wall -funbox-strict-fields
  if impl(ghc >= 6.8)
    ghc-options: -fwarn-tabs
  if flag(fast)
    ghc-options: -O0
  else
    ghc-options: -O2

  if flag(embed-data-files)
    other-modules: Criterion.EmbeddedData
    build-depends: file-embed < 0.1,
                   template-haskell
    cpp-options: "-DEMBED"

Executable criterion-report
  Default-Language:     Haskell2010
  GHC-Options:          -Wall -rtsopts
  Main-Is:              Report.hs
  Other-Modules:        Options
                        Paths_criterion
  Hs-Source-Dirs:       app

  Build-Depends:
    base,
    base-compat-batteries,
    criterion,
    optparse-applicative >= 0.13

  if impl(ghc < 7.6)
    build-depends:
      ghc-prim

  if !impl(ghc >= 8.0)
    build-depends:
      semigroups

test-suite sanity
  type:                 exitcode-stdio-1.0
  hs-source-dirs:       tests
  main-is:              Sanity.hs
  default-language:     Haskell2010
  ghc-options:          -Wall -rtsopts
  if flag(fast)
    ghc-options:        -O0
  else
    ghc-options:        -O2

  build-depends:
    HUnit,
    base,
    bytestring,
    criterion,
    deepseq,
    tasty,
    tasty-hunit

test-suite tests
  type:                 exitcode-stdio-1.0
  hs-source-dirs:       tests
  main-is:              Tests.hs
  default-language:     Haskell2010
  other-modules:        Properties

  ghc-options:
    -Wall -threaded     -O0 -rtsopts

  build-depends:
    QuickCheck >= 2.4,
    base,
    base-compat-batteries,
    criterion,
    statistics,
    HUnit,
    tasty,
    tasty-hunit,
    tasty-quickcheck,
    vector,
    aeson

test-suite cleanup
  type:                 exitcode-stdio-1.0
  hs-source-dirs:       tests
  default-language:     Haskell2010
  main-is:              Cleanup.hs

  ghc-options:
    -Wall -threaded     -O0 -rtsopts

  build-depends:
    HUnit,
    base,
    base-compat,
    bytestring,
    criterion,
    deepseq,
    directory,
    tasty,
    tasty-hunit

source-repository head
  type:     git
  location: https://github.com/haskell/criterion.git

一种选择是使用 dependency-solved REPL。

% cabal repl --build-depends criterion
> :l Fibber.hs

另一个是创建一个 cabal 包。

% mkdir Fibber
% mv Fibber.hs Fibber
% cd Fibber
% cabal init
<follow prompts, and say you're building an executable with filename Fibber.hs>
% vi Fibber.cabal
<add criterion to the build-depends: stanza>
% cabal run Fibber # or whatever executable name you gave it in the cabal init step

后者的一个优点是,当您发现额外的依赖项时,您可以将所有依赖项记录在一个文件中,而不必每次开始时都记住传递所有依赖项 cabal repl.另一个是您的代码将被编译,而不是被解释;对于性能测试相当重要,如果您需要标准,这大概就是您正在做的事情!

5 分钟 Cabal 解释

只是对 cabal 工作原理的简短而非完整的解释,以便您理解错误。

一个项目由组件组成:库和“运行nables”(为便于解释而命名)。每个组件在 build-depends 标签下都有 自己的 组依赖项。

库没有入口点,因此它们不需要 main 函数,也不需要指定 main-is。 cabal 文件只有 one public library,这是包的名称,在文件顶部标记为 name。您可以拥有内部库,但那是另一个话题。在 criterion 的 cabal 文件中,您会看到:

  • namecriterion(第一行)
  • library 标签下的库构建配置。 (依赖项、暴露的模块等...)

Runnables 确实需要一个 main 函数和一个标记为 main-is 的模块。 Runnables 可以是可执行文件、测试套件或基准测试。 AFAIK,将 运行nable 标记为其中之一,只会影响通过命令 cabal compiles/install 它们的方式:cabal buildcabal runcabal test , cabal bench, 等等... 例如 cabal build 将编译(默认情况下)库组件和可执行组件,每个可执行文件生成一个二进制文件,但 cabal test 将编译库组件和测试组件,然后 运行 测试。理论上你可以使用 Criterion.Main.defaultMain 作为普通可执行文件的主要功能(在 cabal 文件中定义为 en 可执行组件),在这种情况下,cabal 将生成一个独立的 exe,它将 运行 执行基准.

criterion 的 cabal 文件中,您在可执行和三个测试套件中看到:

  • app 文件夹下名为 criterion-report 的可执行文件
  • 测试套件 sanitytestcleanuptest 文件夹下,每个都有不同的主要文件

每个组件都有不同的依赖集,特别是,它们都依赖于 criterion library 这是在同一个 cabal 文件中定义的组件在 library 部分下,如上所述。

那么我的错误是怎么回事?

所以本质上,您希望 Fibber.hs 文件是一个“运行nable”文件,作为独立的二进制文件或作为项目中的基准套件。因此,您需要根据需要将其添加到 cabal 文件中。作为建议,避免将其添加到 criterion 的 cabal 文件中,否则您将从源代码编译 criterion。按照@DanielWagner 的回答并创建一个新项目,其中 Fibber.hs 是一个主文件,并将 criterion 添加到其依赖项中,或者使用 dependency-repl 替代项。