Jon*_*Jon 8 java benchmarking jmh
我的最终目标是使用标准Java集合作为基线,为几个Java原始集合库创建一套全面的基准.在过去,我使用了编写这些微基准的循环方法.我把我在基准测试中的函数放在循环中并迭代100万次以上,这样jit就有机会进行预热.我获取循环的总时间,然后除以迭代次数,以估计单次调用我正在进行基准测试的函数所花费的时间.在最近阅读了关于JMH项目,特别是这个例子之后:JMHSample_11_Loops我看到了这种方法的问题.
我的机器:
Windows 7 64-bit
Core i7-2760QM @ 2.40 GHz
8.00 GB Ram
jdk1.7.0_45 64-bit
Run Code Online (Sandbox Code Playgroud)
这是上面描述的循环方法代码的简单示例:
public static void main(String[] args) {
HashMap<Long, Long> hmap = new HashMap<Long, Long>();
long val = 0;
//populating the hashmap
for (long idx = 0; idx < 10000000; idx++) {
hmap.put(idx, idx);
}
Stopwatch s = Stopwatch.createStarted();
long x = 0;
for (long idx = 0; idx < 10000000; idx++) {
x = hmap.get(idx);
}
s.stop();
System.out.println(s); //5.522 s
System.out.println(x); //9999999
//5.522 seconds / 10000000 = 552.2 nanoseconds
}
Run Code Online (Sandbox Code Playgroud)
以下是我尝试使用JMH重写此基准测试:
package com.test.benchmarks;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
@State(Scope.Thread)
public class MyBenchmark {
private HashMap<Long, Long> hmap = new HashMap<Long, Long>();
private long key;
@Setup(Level.Iteration)
public void setup(){
key = 0;
for(long i = 0; i < 10000000; i++) {
hmap.put(i, i);
}
}
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public long testGetExistingKey() throws InterruptedException{
if(key >= 10000000) key=0;
return hmap.get(key++);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(".*" + MyBenchmark.class.getSimpleName() + ".*")
.warmupIterations(5)
.measurementIterations(25)
.forks(1)
.build();
new Runner(opt).run();
}
}
Run Code Online (Sandbox Code Playgroud)
结果如下:
Result: 31.163 ±(99.9%) 11.732 ns/op [Average]
Statistics: (min, avg, max) = (0.000, 31.163, 939008.000), stdev = 1831.428
Confidence interval (99.9%): [19.431, 42.895]
Samples, N = 263849
mean = 31.163 ±(99.9%) 11.732 ns/op
min = 0.000 ns/op
p( 0.0000) = 0.000 ns/op
p(50.0000) = 0.000 ns/op
p(90.0000) = 0.000 ns/op
p(95.0000) = 427.000 ns/op
p(99.0000) = 428.000 ns/op
p(99.9000) = 428.000 ns/op
p(99.9900) = 856.000 ns/op
p(99.9990) = 9198.716 ns/op
p(99.9999) = 939008.000 ns/op
max = 939008.000 ns/op
# Run complete. Total time: 00:02:07
Benchmark Mode Samples Score Score error Units
c.t.b.MyBenchmark.testGetExistingKey sample 263849 31.163 11.732 ns/op
Run Code Online (Sandbox Code Playgroud)
据我所知,JMH中的相同基准测试使得hashmap在31纳秒时达到552纳秒,用于循环测试.31纳秒对我来说似乎有点太快了.查看延迟数,每个程序员都应该知道主存储器参考大约是100纳秒.L2缓存引用大约为7纳秒,但具有1000万个长密钥和值的HashMap远远超过L2.JMH的结果对我来说也很奇怪.90%的get调用需要0.0纳秒?
我假设这是用户错误.任何帮助/指针将不胜感激.谢谢.
UPDATE
以下是AverageTime运行的结果.这更符合我的期望.谢谢@ oleg-estekhin!在下面的评论中,我提到我之前已经完成了AverageTime测试并且得到了类似的结果SampleTime.我相信在那次运行中,我使用的HashMap条目少得多,而且查找速度更快.
Result: 266.306 ±(99.9%) 139.359 ns/op [Average]
Statistics: (min, avg, max) = (27.266, 266.306, 1917.271), stdev = 410.904
Confidence interval (99.9%): [126.947, 405.665]
# Run complete. Total time: 00:07:17
Benchmark Mode Samples Score Score error Units
c.t.b.MyBenchmark.testGetExistingKey avgt 100 266.306 139.359 ns/op
Run Code Online (Sandbox Code Playgroud)
首先,循环测试测量平均时间,而JMH代码配置为采样时间.来自Mode.SampleTimejavadoc:
采样时间:采样每次操作的时间.
Map.get()由于时间测量的粒度,基础时间测量系统将为某些执行报告0,因此个别执行非常快(有关更多信息,请阅读JMH作者对Nanotime博客文章的Nanotrusting).
在样本模式中,基准测试将单个采样时间收集到一个数组中,然后使用该数组计算平均值和百分位数.当超过一半的数组值为零时(在您的特定设置中,超过90%的数组值为零,如图所示p(90.0000) = 0.000 ns/op),平均值必然相当低,但当您在输出中看到p(50) = 0(尤其是p(90) = 0)只有你可以得到可靠的结论是这些结果是垃圾,你需要找到另一种方法来衡量该代码.
您应该使用Mode.AverageTime(或Mode.Throughput)基准模式.Mode.SampleTime在个别调用需要大量时间的情况下离开.
你可以添加一个"基线"基准来执行,if ()并且key++为了隔离key簿记所需的时间和实际Map.get()时间,但你需要解释结果(上面链接的博客文章描述了从"真实"中减去"基线"的陷阱" 测量).
您可以尝试使用Blackhole.consumeCPU()来增加单个调用的执行时间(请参阅前面关于"基线"和相关陷阱的观点).
| 归档时间: |
|
| 查看次数: |
2227 次 |
| 最近记录: |