在 JVM 中分析块的正确方法?
Correct way to profile a block in JVM?
我看到很多帖子建议使用类似于下面的代码来分析 Scala 中的方法:
def timer[A](block: => A): A = {
val startTime = System.currentTimeMillis()
val ret = block
println(s"${System.currentTimeMillis() - startTime}ms")
ret
}
理由是 pass-by-name
为 block
。但是,当我尝试代码片段时,我发现结果不可靠。
object Euler006 extends App {
def f1(n: Int): Long = {
val numbers = 1 to n
def square(n: Int) = n.toLong * n
square(numbers.sum) - numbers.map(square).sum
}
def f2(n: Int): Long = (n.toLong - 1) * n / 2 * (n + 1) / 3 * (3 * n + 2) / 2
{
val r2 = timer(f2(10000))
val r1 = timer(f1(10000))
println(s"$r1 $r2")
}
System.gc()
System.runFinalization()
{
val r1 = timer(f1(10000))
val r2 = timer(f2(10000))
println(s"$r1 $r2")
}
}
输出:
57ms // line 1
19ms // line 2
2500166641665000 2500166641665000
7ms // line 4
0ms // line 5
2500166641665000 2500166641665000
显然 f2
应该花很少的时间来执行,但是第 1 行输出 57ms
。我想这可能是因为 JVM 初始化。 OTOH,尽管我尝试了垃圾收集,但第 2 行和第 4 行也有所不同(我们不能保证,因为 JVM 有一些不确定性,但这就是我能想到的)。
这个例子非常简单,我应该多次 运行 结果以实际分析它们(就像 Python 中的 timeit
模块)。但是,我不确定如何将正确的计时器写入 eliminate/mitigate 输出中显示的潜在影响。
更新:
JVM 初始化应该包括在内,因为如果我在开始之前添加类似 timer({})
的内容,第 1 行 时间成本很快就会变成 0ms
(表示它花费的时间很少)。
Obviously f2 should take little time to execute,
这可能需要一些时间,但不一定需要一毫秒。您的计算可能只有几分之一毫秒。事实上,由于没有使用结果,代码可以被丢弃。
我建议你使用 System.nanoTime()
并确保使用结果。
guess perhaps it's because of the JVM initialization.
您第一次调用代码时,必须加载它,很可能这就是您所安排的时间
line 2 and line 4 are also different although I tried garbage collection
代码现已加载。注意:如果您复制了代码和 运行 另一个做同样事情的方法,您可能会得到相似的时间。
I should run the results for many times to actually profile them
我会忽略 运行 的前 2 秒确保代码已经预热,或者使用像 JMH 这样的微基准框架 http://openjdk.java.net/projects/code-tools/jmh/
static int n = 10000;
public static void main(String[] args) throws RunnerException, IOException {
long time = 2;
Options opt = new OptionsBuilder()
.include(CalcBenchmark.class.getSimpleName())
.warmupIterations(6)
.forks(1)
.measurementTime(TimeValue.seconds(time))
.timeUnit(TimeUnit.NANOSECONDS)
.build();
new Runner(opt).run();
}
@Benchmark
public long calc() {
return (n - 1L) * n / 2 * (n + 1) / 3 * (3 * n + 2) / 2;
}
打印
# JMH 1.11.2 (released 164 days ago, please consider updating!)
# VM version: JDK 1.8.0_45, VM 25.45-b02
# VM invoker: /mnt/opt/jdk1.8.0_45/jre/bin/java
# VM options: -Didea.launcher.port=7534 -Didea.launcher.bin.path=/mnt/opt/idea-IC-143.1821.5/bin -Dfile.encoding=UTF-8
# Warmup: 6 iterations, 1 s each
# Measurement: 20 iterations, 2 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: vanilla.java.jmh.CalcBenchmark.calc
# Run progress: 0.00% complete, ETA 00:00:46
# Fork: 1 of 1
# Warmup Iteration 1: 0.105 ops/ns
# Warmup Iteration 2: 0.156 ops/ns
# Warmup Iteration 3: 0.169 ops/ns
# Warmup Iteration 4: 0.167 ops/ns
# Warmup Iteration 5: 0.166 ops/ns
# Warmup Iteration 6: 0.165 ops/ns
Iteration 1: 0.169 ops/ns
Iteration 2: 0.166 ops/ns
Iteration 3: 0.165 ops/ns
Iteration 4: 0.168 ops/ns
Iteration 5: 0.163 ops/ns
Iteration 6: 0.159 ops/ns
Iteration 7: 0.162 ops/ns
Iteration 8: 0.166 ops/ns
Iteration 9: 0.169 ops/ns
Iteration 10: 0.166 ops/ns
Iteration 11: 0.169 ops/ns
Iteration 12: 0.162 ops/ns
Iteration 13: 0.166 ops/ns
Iteration 14: 0.167 ops/ns
Iteration 15: 0.166 ops/ns
Iteration 16: 0.169 ops/ns
Iteration 17: 0.166 ops/ns
Iteration 18: 0.165 ops/ns
Iteration 19: 0.170 ops/ns
Iteration 20: 0.164 ops/ns
Result "calc":
0.166 ±(99.9%) 0.002 ops/ns [Average]
(min, avg, max) = (0.159, 0.166, 0.170), stdev = 0.003
CI (99.9%): [0.163, 0.168] (assumes normal distribution)
# Run complete. Total time: 00:00:47
Benchmark Mode Cnt Score Error Units
CalcBenchmark.calc thrpt 20 0.166 ± 0.002 ops/ns
简而言之,您的操作一旦预热,大约需要 6 ns 或 0.000006 ms
没有 JMH 的更简单的基准测试可能如下所示。注:我更相信JMH的数字。
public class SimpleCalcBenchmark {
static int n = 10000;
static final AtomicLong blackHole = new AtomicLong();
public static void main(String[] args) throws RunnerException, IOException {
for (int i = 0; i < 5; i++) {
long start = System.nanoTime();
long counter = 0;
while (System.nanoTime() - start < 2e9) {
for (int j = 0; j < 100; j++) {
blackHole.lazySet(calc());
}
counter += 100;
}
long time = System.nanoTime() - start;
System.out.printf("Took an average of %.1f ns%n", (double) time/counter);
}
}
public static long calc() {
return (n - 1L) * n / 2 * (n + 1) / 3 * (3 * n + 2) / 2;
}
}
打印
Took an average of 10.2 ns
Took an average of 6.7 ns
Took an average of 4.7 ns
Took an average of 4.7 ns
Took an average of 4.6 ns
我看到很多帖子建议使用类似于下面的代码来分析 Scala 中的方法:
def timer[A](block: => A): A = {
val startTime = System.currentTimeMillis()
val ret = block
println(s"${System.currentTimeMillis() - startTime}ms")
ret
}
理由是 pass-by-name
为 block
。但是,当我尝试代码片段时,我发现结果不可靠。
object Euler006 extends App {
def f1(n: Int): Long = {
val numbers = 1 to n
def square(n: Int) = n.toLong * n
square(numbers.sum) - numbers.map(square).sum
}
def f2(n: Int): Long = (n.toLong - 1) * n / 2 * (n + 1) / 3 * (3 * n + 2) / 2
{
val r2 = timer(f2(10000))
val r1 = timer(f1(10000))
println(s"$r1 $r2")
}
System.gc()
System.runFinalization()
{
val r1 = timer(f1(10000))
val r2 = timer(f2(10000))
println(s"$r1 $r2")
}
}
输出:
57ms // line 1
19ms // line 2
2500166641665000 2500166641665000
7ms // line 4
0ms // line 5
2500166641665000 2500166641665000
显然 f2
应该花很少的时间来执行,但是第 1 行输出 57ms
。我想这可能是因为 JVM 初始化。 OTOH,尽管我尝试了垃圾收集,但第 2 行和第 4 行也有所不同(我们不能保证,因为 JVM 有一些不确定性,但这就是我能想到的)。
这个例子非常简单,我应该多次 运行 结果以实际分析它们(就像 Python 中的 timeit
模块)。但是,我不确定如何将正确的计时器写入 eliminate/mitigate 输出中显示的潜在影响。
更新:
JVM 初始化应该包括在内,因为如果我在开始之前添加类似 timer({})
的内容,第 1 行 时间成本很快就会变成 0ms
(表示它花费的时间很少)。
Obviously f2 should take little time to execute,
这可能需要一些时间,但不一定需要一毫秒。您的计算可能只有几分之一毫秒。事实上,由于没有使用结果,代码可以被丢弃。
我建议你使用 System.nanoTime()
并确保使用结果。
guess perhaps it's because of the JVM initialization.
您第一次调用代码时,必须加载它,很可能这就是您所安排的时间
line 2 and line 4 are also different although I tried garbage collection
代码现已加载。注意:如果您复制了代码和 运行 另一个做同样事情的方法,您可能会得到相似的时间。
I should run the results for many times to actually profile them
我会忽略 运行 的前 2 秒确保代码已经预热,或者使用像 JMH 这样的微基准框架 http://openjdk.java.net/projects/code-tools/jmh/
static int n = 10000;
public static void main(String[] args) throws RunnerException, IOException {
long time = 2;
Options opt = new OptionsBuilder()
.include(CalcBenchmark.class.getSimpleName())
.warmupIterations(6)
.forks(1)
.measurementTime(TimeValue.seconds(time))
.timeUnit(TimeUnit.NANOSECONDS)
.build();
new Runner(opt).run();
}
@Benchmark
public long calc() {
return (n - 1L) * n / 2 * (n + 1) / 3 * (3 * n + 2) / 2;
}
打印
# JMH 1.11.2 (released 164 days ago, please consider updating!)
# VM version: JDK 1.8.0_45, VM 25.45-b02
# VM invoker: /mnt/opt/jdk1.8.0_45/jre/bin/java
# VM options: -Didea.launcher.port=7534 -Didea.launcher.bin.path=/mnt/opt/idea-IC-143.1821.5/bin -Dfile.encoding=UTF-8
# Warmup: 6 iterations, 1 s each
# Measurement: 20 iterations, 2 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: vanilla.java.jmh.CalcBenchmark.calc
# Run progress: 0.00% complete, ETA 00:00:46
# Fork: 1 of 1
# Warmup Iteration 1: 0.105 ops/ns
# Warmup Iteration 2: 0.156 ops/ns
# Warmup Iteration 3: 0.169 ops/ns
# Warmup Iteration 4: 0.167 ops/ns
# Warmup Iteration 5: 0.166 ops/ns
# Warmup Iteration 6: 0.165 ops/ns
Iteration 1: 0.169 ops/ns
Iteration 2: 0.166 ops/ns
Iteration 3: 0.165 ops/ns
Iteration 4: 0.168 ops/ns
Iteration 5: 0.163 ops/ns
Iteration 6: 0.159 ops/ns
Iteration 7: 0.162 ops/ns
Iteration 8: 0.166 ops/ns
Iteration 9: 0.169 ops/ns
Iteration 10: 0.166 ops/ns
Iteration 11: 0.169 ops/ns
Iteration 12: 0.162 ops/ns
Iteration 13: 0.166 ops/ns
Iteration 14: 0.167 ops/ns
Iteration 15: 0.166 ops/ns
Iteration 16: 0.169 ops/ns
Iteration 17: 0.166 ops/ns
Iteration 18: 0.165 ops/ns
Iteration 19: 0.170 ops/ns
Iteration 20: 0.164 ops/ns
Result "calc":
0.166 ±(99.9%) 0.002 ops/ns [Average]
(min, avg, max) = (0.159, 0.166, 0.170), stdev = 0.003
CI (99.9%): [0.163, 0.168] (assumes normal distribution)
# Run complete. Total time: 00:00:47
Benchmark Mode Cnt Score Error Units
CalcBenchmark.calc thrpt 20 0.166 ± 0.002 ops/ns
简而言之,您的操作一旦预热,大约需要 6 ns 或 0.000006 ms
没有 JMH 的更简单的基准测试可能如下所示。注:我更相信JMH的数字。
public class SimpleCalcBenchmark {
static int n = 10000;
static final AtomicLong blackHole = new AtomicLong();
public static void main(String[] args) throws RunnerException, IOException {
for (int i = 0; i < 5; i++) {
long start = System.nanoTime();
long counter = 0;
while (System.nanoTime() - start < 2e9) {
for (int j = 0; j < 100; j++) {
blackHole.lazySet(calc());
}
counter += 100;
}
long time = System.nanoTime() - start;
System.out.printf("Took an average of %.1f ns%n", (double) time/counter);
}
}
public static long calc() {
return (n - 1L) * n / 2 * (n + 1) / 3 * (3 * n + 2) / 2;
}
}
打印
Took an average of 10.2 ns
Took an average of 6.7 ns
Took an average of 4.7 ns
Took an average of 4.7 ns
Took an average of 4.6 ns