我为get
的remove
和HashMap
编写了一个基准,如下所示:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class HashMapBenchmark {
@State(Scope.Benchmark)
public static class Mystate {
HashMap<String,String> hashmapVar = new HashMap<String,String>();
String key0 = "bye";
@Setup(Level.Iteration)
public void setup(){
hashmapVar.put(key0,"bubye");
}
}
@Benchmark
public void hashmapGet(Mystate state ,Blackhole bh) {
bh.consume(state.hashmapVar.get(state.key0));
}
@Benchmark
public void hashmapRemove(Mystate state ,Blackhole bh) {
bh.consume(state.hashmapVar.remove(state.key0));
}
}
它产生以下结果:
Benchmark Mode Samples Score Score error Units
c.b.HashMapBenchmark.hashmapGet avgt 60 6.348 0.320 ns/op
c.b.HashMapBenchmark.hashmapRemove avgt 60 5.180 0.074 ns/op
根据结果,
remove()
比get()
快一点。即使要删除一个元素,首先也必须检索该元素,不是吗?
remove()
如何更快?还是我错过了什么?更新
使用最新的JMH(1.11.3)之后,结果如下:
Benchmark Mode Cnt Score Error Units
HashMapBenchmark.hashmapGet avgt 60 9.713 ± 0.277 ns/op
HashMapBenchmark.hashmapRemove avgt 60 7.677 ± 0.166 ns/op
最佳答案
因此麻烦在于,这些基准测试测量的是不同的东西:来自填充 map 的get()
和来自(最终)空 map 的remove()
。比较是没有意义的,您可能会放弃基准。
您必须保证针对相同的HashMap
完成该操作。不幸的是,这需要要么使用@Setup(Invocation)
本身就不好(请阅读Javadoc!),要么将HashMap
的构建成本吸收到基准测试本身中:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class HashMapBenchmark {
@Benchmark
public String get() {
HashMap<String, String> hm = createMap();
return hm.get("bye");
}
@Benchmark
public String remove() {
HashMap<String, String> hm = createMap();
return hm.remove("bye");
}
// extra protection from optimization
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
private HashMap<String, String> createMap() {
HashMap<String, String> hm = new HashMap<>();
hm.put("bye", "bye");
return hm;
}
}
您可以格外小心,将 map 创建放到一个单独的不可内联方法中:当今的编译器无法在调用之间进行优化。在我的i7-4790K,4.0 GHz,Linux x86_64,JDK 8u66上:
Benchmark Mode Cnt Score Error Units
HashMapBenchmark.get avgt 15 24.343 ± 0.351 ns/op
HashMapBenchmark.remove avgt 15 24.611 ± 0.369 ns/op
没有太大的区别。实际上,如果您使用
-prof perfasm
查看生成的代码,则会在其中产生一些可量化的差异。或者,您可以使用-prof perfnorm
快速描述这两种工作负载。请注意,这种情况无法回答在真实 map 上是一种方法还是另一种方法更好。可以为两个参数都作参数:
get
不会修改映射,因此不会引起内存存储,remove
可以帮助加载因子,以便下一个remove
可以更快,等等。单个基准和一段文本距离很远任何富有成果的讨论。