为什么独立代码块的执行时间取决于Scala中的执行顺序?

时间:2016-05-05 11:24:06

标签: scala jvm measurement

我有一个用Scala编写的程序。我想测量不同独立代码块的执行时间。当我以明显的方式(即在每个块之前和之后插入System.nanoTime())时,我观察到执行时间取决于块的顺序。前几个块总是比其他块花费更多时间。

我创建了一个再现此行为的简约示例。为简单起见,所有代码块都是相同的,并为整数数组调用hashCode()

package experiments

import scala.util.Random

/**
  * Measuring execution time of a code block
  *
  * Minimalistic example
  */
object CodeBlockMeasurement {

  def main(args: Array[String]): Unit = {
    val numRecords = args(0).toInt
    // number of independent measurements
    val iterations = args(1).toInt

    // Changes results a little bit, but not too much
    // val records2 = Array.fill[Int](1)(0)
    // records2.foreach(x => {})

    for (_ <- 1 to iterations) {
      measure(numRecords)
    }
  }

  def measure(numRecords: Int): Unit = {
    // using a new array every time
    val records = Array.fill[Int](numRecords)(new Random().nextInt())
    // block of code to be measured
    def doSomething(): Unit = {
      records.foreach(k => k.hashCode())
    }
    // measure execution time of the code-block
    elapsedTime(doSomething(), "HashCodeExperiment")
  }

  def elapsedTime(block: => Unit, name: String): Unit = {
    val t0 = System.nanoTime()
    val result = block
    val t1 = System.nanoTime()
    // print out elapsed time in milliseconds
    println(s"$name took ${(t1 - t0).toDouble / 1000000} ms")
  }
}

使用numRecords = 100000iterations = 10运行程序后,我的控制台如下所示:

  

HashCodeExperiment耗时14.630283毫秒   HashCodeExperiment花了7.125693毫秒   HashCodeExperiment耗时0.368151毫秒   HashCodeExperiment耗时0.431628毫秒   HashCodeExperiment耗时0.086455毫秒   HashCodeExperiment花了0.056458 ms
  HashCodeExperiment花了0.055138 ms
  HashCodeExperiment耗时0.062997毫秒   HashCodeExperiment耗时0.063736毫秒   HashCodeExperiment花了0.056682 ms

有人可以解释为什么会这样吗?不应该都一样吗?哪个是真正的执行时间?

非常感谢,
彼得

  

环境参数:
   OS:ubuntu 14.04 LTS(64位)
   IDE:IntelliJ IDEA 2016.1.1(IU-145.597)
   Scala:2.11.7

2 个答案:

答案 0 :(得分:6)

Java的JIT开始使用。最初执行普通字节码但在一段时间后(默认情况下,对于Oracle JVM, 1.5k / 10k调用,请参阅{{3 } )优化开始处理实际执行的本机代码,这通常会带来非常显着的性能提升。

正如Ivan所提到的,那里有中间字节码/本机代码和各种其他技术的缓存,其中最重要的一个是垃圾收集器本身,它会导致个别结果的更多变化。根据代码分配新对象的程度,这可能绝对会在GC发生时废弃性能,但这是一个单独的问题。

要在微基准测试时删除此类异常值结果,建议您对操作的多次迭代进行基准测试,并丢弃底部和前5..10%的结果,并根据剩余样本进行性能评估。

答案 1 :(得分:5)

简短回答:缓存。

这些是独立的代码块,但运行不能完全独立,因为它们在同一个JVM实例中运行,并且在同一CPU的同一进程中运行。 JVM本身内部有很多优化,包括缓存。现代CPU也这样做。因此,这是一种非常常见的行为,重新运行通常比首次运行花费的时间更少。