我想在我的 Scala 应用程序中对几个方法的运行时进行基准测试,我正在研究使用 ScalaMeter。假设我想测量一个名为doSomething()
的方法的时间。
我只想打电话给doSomething
并测量运行一次所需的时间。但是,我看到的所有 ScalaMeter 文档都需要提供某种输入,无论是一系列整数、字符串还是其他东西。
是否可以使用 ScalaMeter 来执行我的要求?这是一个合适的用例吗?
这是可能的,但这将是浪费时间。
您可能知道,ScalaMeter旨在消除函数执行时间变化的影响,以便可以准确地对这些执行时间进行基准测试。例如,您可能希望验证函数是否在所需时间内完成,或者确定在对基本代码进行更改时是否随时间推移保持其性能。
为什么这如此具有挑战性?嗯,有许多障碍需要克服:
- JVM有许多不同的选项用于在程序中执行生成的Java字节码。有些(例如零 VM)只是解释代码;其他人利用即时(JIT)编译来优化对主机 CPU机器代码的转换;HotSpot 服务器 VM会随着时间的推移显著提高性能,因此代码运行时间越长,性能就会逐步提高。出于基准测试目的,HotSpot 客户端虚拟机执行了非常好的优化并快速达到稳定状态,因此使我们能够快速开始测量性能。但是,我们仍然需要允许JIT编译器预热,因此我们必须忽略前几个较慢的执行(运行),否则会偏向我们的结果。ScalaMeter在自行进行此预热方面做得很好,但是要丢弃的运行次数是可配置的。
- JVM执行许多垃圾回收(GC)循环,看似随机,这在发生时同样会降低性能。可以将 ScalaMeter配置为忽略发生GC周期的执行。
- 当主机执行来自同一台计算机上运行的其他进程的线程时,主机的负载可能会有所不同。这些还可能减慢执行时间。ScalaMeter通过仅考虑固定运行次数中最快的观察时间而不是取平均值来处理此问题。
- 如果您从SBT运行,则分叉的JVM 执行会话将比与 SBT 共享相同 JVM 实例的执行会话执行会话性能更好,并且变化更小(因为将使用更多的SBT JVM资源)。
- 虚拟内存页错误(其中构成应用程序工作集的内存与分页文件切换/从分页文件切换)也会随机影响性能。
- 许多函数的性能将取决于它的参数(如果你不喜欢函数式编程,共享互斥状态)。将性能与参数值联系起来也是ScalaMeter擅长的事情,通过使用生成器。(例如,考虑对
List
执行size
操作 — 随着List
中元素数量的增加,执行时间显然会更长。 - 等。您可以在ScalaMeter 入门简介中找到有关这些问题的更多信息。
显然,基准测试应该在同一台主机上执行,以便结果具有可比性,因为CPU,操作系统,内存,BIOS配置等也会影响性能。
因此,在解释了所有这些之后,您将了解为什么ScalaMeter需要执行相同的功能!;-)
在您的情况下,doSomething()
不带任何参数,因此您可以使用Gen[T].single
生成器来标识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()
}
您只会执行一次该函数,并且每次花费的时间都会大不相同。