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

Why do the execution time of independent code blocks depend on execution order in Scala?

本问题已经有最佳答案,请猛点这里访问。

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

我创造了一个复制这种行为的最低限度的例子。所有代码块都是相同的,为了简单起见,对于整数数组调用hashCode()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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 took 14.630283 ms
HashCodeExperiment took 7.125693 ms
HashCodeExperiment took 0.368151 ms
HashCodeExperiment took 0.431628 ms
HashCodeExperiment took 0.086455 ms
HashCodeExperiment took 0.056458 ms
HashCodeExperiment took 0.055138 ms
HashCodeExperiment took 0.062997 ms
HashCodeExperiment took 0.063736 ms
HashCodeExperiment took 0.056682 ms

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

谢谢,彼得

Environment parameters:
OS: ubuntu 14.04 LTS (64 bit)
IDE: IntelliJ IDEA 2016.1.1 (IU-145.597)
Scala: 2.11.7


这是Java的JIT。最初执行普通字节码,但经过一段时间(Oracle JVM默认为1.5K/10K调用,请参阅-XX:CompileThreshold),优化开始处理实际执行的本机代码,这通常会导致相当大的性能改进。

正如Ivan所提到的,中间字节码/本机代码以及其他各种相关技术都有缓存,其中最重要的一项是垃圾收集器本身,这会导致对单个结果的更大差异。根据代码分配新对象的程度,这可能会在每次发生GC时完全破坏性能,但这是一个单独的问题。

要在微标记时删除这些异常值结果,建议您对操作的多个迭代进行基准测试,并放弃底部和顶部5..10%的结果,并根据剩余样本进行性能估计。


简短回答:缓存。

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