我可以在没有输入的情况下使用 ScalaMeter 吗?

Can I use ScalaMeter with no input?

我想对我的 Scala 应用程序中几种方法的 运行时间进行基准测试,我正在研究使用 ScalaMeter。假设我想测量一个名为 doSomething().

的方法的时间

我只想调用doSomething并测量一次运行所花费的时间。但是,我看到的所有 ScalaMeter 文档都需要提供某种输入,无论是一系列整数、字符串还是其他内容。

是否可以使用 ScalaMeter 来完成我的要求?这是一个合适的用例吗?

可以,但会浪费时间。

您可能已经知道,ScalaMeter 旨在消除函数执行时间变化的影响,因此可以准确地对这些执行时间进行基准测试。例如,您可能想要验证函数是否在要求的时间内完成,或者确定其性能是否随着代码库的更改而保持不变。

为什么这么有挑战性?那么,有许多障碍需要克服:

  1. JVM 有许多不同的选项用于在程序中执行生成的 Java 字节码。有些(例如 Zero VM)只是解释代码;其他人利用 just-in-time (JIT) 编译来优化翻译到主机 CPU 的 机器代码HotSpot Server VM 随着时间的推移积极提高性能,因此代码性能会随着运行时间的延长而逐步提高。出于基准测试目的,HotSpot Client VM 执行了非常好的优化并很快达到 steady-state,因此我们可以快速开始测量性能。但是,我们仍然需要让 JIT 编译器预热,因此我们必须忽略前几次较慢的执行 (runs)否则会使我们的结果产生偏差。 ScalaMeter 本身就很好地完成了这个 warmup,但是要丢弃的运行次数是可配置的。
  2. JVM 执行多个 垃圾收集 (GC) 周期,似乎在随机,当它们发生时同样会降低性能。 ScalaMeter 可以配置为忽略发生 GC 循环的执行。
  3. 主机的负载可能会有所不同,因为它在同一台机器上执行来自其他进程 运行 的线程。这些也可能会减慢执行时间。 ScalaMeter 通过仅考虑固定运行次数中观察到的最快时间来处理此问题,而不是取平均值。
  4. 如果您是来自 SBT 的 运行,forked JVM 执行会话将执行得更好,变化更少,而不是与 SBT 共享相同 JVM 实例的实例(因为更多 SBT JVM资源将被使用)。
  5. 虚拟内存页面错误(其中构成应用程序工作集的内存被切换to/from a 分页文件) 也会随机影响性能。
  6. 许多函数的性能将取决于它的参数(而且,如果您不喜欢 函数式编程共享可变状态 ).通过使用 generators,将性能与参数值联系起来也是 ScalaMeter 擅长的事情。 (例如,考虑 List 上的 size 操作——随着 List 中元素数量的增加,执行时间显然会更长。)
  7. 等您可以在 ScalaMeter Getting Started Introduction.
  8. 中找到有关这些问题的更多信息

显然,应该在同一台主机上执行基准测试,这样结果才具有可比性,因为 CPU、OS、内存、BIOS 配置等都会影响性能也是。

所以,在解释了所有这些之后,您就会明白为什么 ScalaMeter 需要执行相同的功能 很多! ;-)

在您的情况下,doSomething() 不接受任何参数,因此您可以使用 Gen[T].single 生成器来标识 class 或 doSomething() 所属的对象,它将看起来像下面这样:

注意:这是作为 ScalaMeter 测试编写的,因此源代码应在 src/test/scala:

import org.scalameter.api._
import org.scalameter.picklers.Implicits._

object MyBenchmark
extends Bench.ForkedTime {

  // We have no arguments. Instead, create a single "generator" that identifies the class or
  // object that doSomething belongs to. This assumes doSomething() belongs to object
  // MyObject.
  val owner = Gen.single("owner")(MyObject) 

  // Measure MyObject.doSomething()'s performance.
  performance of "MyObject" in {
    measure method "doSomething()" in {
      using(owner) in {
        _.doSomething()
      }
    }
  }
}

(顺便说一句:我原以为没有参数的基准测试函数会比这更直接,但这是迄今为止我能想到的最好的。如果有人有更好的主意,请添加评论让我知道!)

所以,如果所有这些都太过分了,您可能想尝试这样的事情:

// Measure nanoseconds taken to execute by name argument.
def measureTime(x: => Unit): Long = {
  val start = System.nanoTime()
  x
  // Calculate how long that took and return the value.
  System.nanoTime() - start
}

measureTime {
  doSomething()
}

你只会执行一次函数,每次所用的时间都会大不相同。