【发布时间】:2013-03-23 07:46:42
【问题描述】:
在对一些高吞吐量数据结构进行内存基准测试时,我意识到我可以使用ImmutableMap,只需进行一点重构。
认为这将是一个改进,我将其投入其中,并惊讶地发现它不仅比 HashMap 慢,在单线程环境中它似乎始终比 ConcurrentHashMap 慢!
你可以看到full benchmark
测试的内容非常简单,计算获取地图中可能存在的大量随机字符串需要多长时间。
public static void timeAccess(Map<String,String> map) {
Random rnd = new Random(seed);
int foundCount = 0;
long start = System.nanoTime();
for(int i = 0; i < loop; i++) {
String s = map.get(RndString.build(rnd));
if(s != null)
foundCount++;
}
long stop = System.nanoTime() - start;
System.out.println("Found "+foundCount+" strings out of "+loop+" attempts - "+
String.format("%.2f",100.0*foundCount/loop)+" success rate.");
System.out.println(map.getClass().getSimpleName()+" took "+
String.format("%.4f", stop/1_000_000_000.0)+" seconds.");
System.out.println();
}
针对包含相同值的HashMap、ConcurrentHashMap 和ImmutableMap 运行此程序,在使用ImmutableMap 时始终显示出显着的减速 - 通常慢 15% 以上。地图越稀疏(即,map.get() 返回 null 的频率越高)差异越大。这是示例运行的结果:
Found 35312152 strings out of 100000000 attempts - 35.31 success rate.
HashMap took 29.4538 seconds.
Found 35312152 strings out of 100000000 attempts - 35.31 success rate.
ConcurrentHashMap took 32.1465 seconds.
Found 35312152 strings out of 100000000 attempts - 35.31 success rate.
RegularImmutableMap took 37.9709 seconds.
这是记录/预期的问题吗? Guava Docs 表示 Immutable*** 内存效率更高,但没有说明速度。对于这种幅度的减速,我倾向于处理内存成本并在速度成为问题时避免Immutable***(什么时候不是?!)。我错过了什么吗?
另见:https://groups.google.com/forum/?fromgroups=#!topic/guava-discuss/I7yPpa5Hlpg
【问题讨论】:
-
该邮件列表线程中提到的问题肯定仍然适用于您的基准测试。此外,请参阅
ImmutableMapJavadoc:“与 HashMap 不同,ImmutableMap 未针对具有慢 Object.equals(java.lang.Object) 或 Object.hashCode() 实现的元素类型进行优化。您可以通过让您的元素获得更好的性能类型缓存它自己的哈希码,并通过使用缓存的值来短路慢速等于算法。”这肯定是String的问题。最后,ImmutableMap的实现与HashMap的实现基本相同。 -
如果你喜欢,你可以尝试一些 Guava 的基准测试:code.google.com/p/guava-libraries/source/browse/guava-tests/…
-
此外,“处理内存成本”可能会导致 GC 负载显着增加,这可能会减慢您的程序的速度,就像更慢但更紧凑的实现一样。使用您的特定实际应用程序进行分析真的无可替代。
-
@LouisWasserman #1 -
String的equals()和hashCode()方法效率低下?这似乎是一个大问题 - 它是用作映射键的主要类型......我对代码的印象是ImmutableMap与HashMap非常相似,就像你说的那样,但是我看到的访问时间不要不同意。 #2 谢谢,我去看看。 -
#1:
String有一个线性时间equals方法——我不会称其为“大问题”,这只是算法定律——它不是使用它的hashCode缓存来短路,这……并不理想,但无论哪种方式都可以争论。 #3:当然,这对你的用例来说是正确的,但是 ImmutableMap 被优化为一个通才,在许多不同的用例中都是好的,如果不是完美的。 (例如,Android 应用程序从 ImmutableMap 的紧凑设计中受益匪浅。)
标签: java performance hashmap benchmarking guava