为什么独立代码块的执行时间取决于 Scala 中的执行顺序?
Why do the execution time of independent code blocks depend on execution order in Scala?
我有一个用 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 = 100000
和 iterations = 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
简答:缓存。
这些是独立的代码块,但是运行s不能完全独立,因为它们在同一个JVM实例中运行,在同一个CPU的同一个进程中。
JVM本身内部有很多优化,包括缓存。现代CPU也是这样做的。因此,这是很常见的行为,re-运行 通常比 first 运行.
花费的时间更少
这是 Java 的 JIT kicking in. Initially the plain bytecode is executed but after some time (1.5k/10k invocations by default for Oracle JVM, see -XX:CompileThreshold
) 优化开始处理实际执行的本机代码,这通常会导致相当大的性能改进。
正如 Ivan 提到的,还有中间 bytecode/native 代码的缓存和涉及的各种其他技术,其中最重要的技术之一是垃圾收集器本身,它会导致个体结果的差异更大。根据代码分配新对象的程度,这可能会在 GC 发生时完全破坏性能,但这是一个单独的问题。
要在进行微基准测试时删除此类异常结果,建议您对操作的多次迭代进行基准测试并丢弃底部和顶部 5..10% 的结果,并根据剩余样本进行性能评估。
我有一个用 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 = 100000
和 iterations = 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
简答:缓存。
这些是独立的代码块,但是运行s不能完全独立,因为它们在同一个JVM实例中运行,在同一个CPU的同一个进程中。 JVM本身内部有很多优化,包括缓存。现代CPU也是这样做的。因此,这是很常见的行为,re-运行 通常比 first 运行.
花费的时间更少这是 Java 的 JIT kicking in. Initially the plain bytecode is executed but after some time (1.5k/10k invocations by default for Oracle JVM, see -XX:CompileThreshold
) 优化开始处理实际执行的本机代码,这通常会导致相当大的性能改进。
正如 Ivan 提到的,还有中间 bytecode/native 代码的缓存和涉及的各种其他技术,其中最重要的技术之一是垃圾收集器本身,它会导致个体结果的差异更大。根据代码分配新对象的程度,这可能会在 GC 发生时完全破坏性能,但这是一个单独的问题。
要在进行微基准测试时删除此类异常结果,建议您对操作的多次迭代进行基准测试并丢弃底部和顶部 5..10% 的结果,并根据剩余样本进行性能评估。