いくつかの高スループットデータ構造のメモリベンチマークに取り組んでいるときに、少しのリファクタリングでImmutableMap
を使用できることに気付きました。
これが改善になると思って、私はそれをミックスに投入し、それがHashMap
よりも遅いだけでなく、シングルスレッド環境ではConcurrentHashMap
!
あなたは見ることができます 完全なベンチマーク
テストの要点は非常に単純で、マップに存在する可能性のある多数のランダムな文字列を取得するのにかかる時間です。
_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
Louis Wassermanが言ったように、ImmutableMap
は、遅いequals
メソッドを持つオブジェクトには最適化されていません。主な違いはここにあると思います:
HashMap :
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
if (key.equals(candidateKey)) {
return entry.getValue();
ご覧のとおり、衝突をチェックするには、HashMap
が最初にハッシュをチェックします。これにより、ハッシュが異なる値をすばやく拒否できます。 String
はequals
メソッドでこのチェックを行わないため、これによりHashMap
が高速になります。 ImmutableMap
は、equals
がすでに最適化されている場合にテストが遅くなるため、この最適化を使用しません。
いくつかの考えられる理由:
それはRndString.build()の実装に依存しますか?
そして、両方のマップのget()実装を見てください。com.google.common.collect.RegularImmutableMap.get(Object)Java.util.HashMap.getEntry(Object)Java.util.HashMapは "==と比較しようとします" 最初。 RegularImmutableMapはそうではありません。それはスピードアップするかもしれません
別の負荷係数がその原因である可能性がありますか?おそらく、RegularImmutableMapは、正しいエントリを見つけるためにさらに反復が必要です。