ConcurrentSkipListMapはいつ使用する必要がありますか?
-
06-07-2019 - |
質問
Javaでは、 multithreading
ソリューションを改善するために ConcurrentHashMap
があります。次に、いつ ConcurrentSkipListMap
を使用する必要がありますか?冗長性ですか?
これら2つの間のマルチスレッドの側面は一般的ですか?
解決
これらの2つのクラスはいくつかの点で異なります。
ConcurrentHashMap は契約の一部としての操作の実行時間を保証しません*。また、特定の負荷係数(大まかに、同時に変更するスレッドの数)を調整することもできます。
ConcurrentSkipListMap 一方、さまざまな操作で平均O(log(n))パフォーマンスを保証します。また、並行性のためのチューニングもサポートしていません。 ConcurrentSkipListMap
には、 ConcurrentHashMap
にはない多くの操作もあります。ceilingEntry/ Key、floorEntry / Keyなどです。また、ソート順序を維持します。 ConcurrentHashMap
を使用している場合は、(かなりの費用で)計算されます。
基本的に、さまざまなユースケースに対してさまざまな実装が提供されます。迅速な単一キー/値ペアの追加と迅速な単一キー検索が必要な場合は、 HashMap
を使用します。より高速な順序走査が必要で、挿入に余分なコストがかかる場合は、 SkipListMap
を使用します。
*実装は、O(1)挿入/ルックアップの一般的なハッシュマップの保証とほぼ一致すると予想していますが、再ハッシュを無視する
他のヒント
データ構造の定義については、スキップリストをご覧ください。 ConcurrentSkipListMapは、マップをそのキーの自然な順序(または定義した他のキー順序)で保存します。そのため、HashMapよりもget / put / contains操作が遅くなりますが、これを相殺するために、SortedMapおよびNavigableMapインターフェースをサポートしています。
パフォーマンスの観点から、 skipList
をMapとして使用すると、10〜20倍遅くなります。これが私のテストの結果です(Java 1.8.0_102-b14、x32に勝ちます)
Benchmark Mode Cnt Score Error Units
MyBenchmark.hasMap_get avgt 5 0.015 ? 0.001 s/op
MyBenchmark.hashMap_put avgt 5 0.029 ? 0.004 s/op
MyBenchmark.skipListMap_get avgt 5 0.312 ? 0.014 s/op
MyBenchmark.skipList_put avgt 5 0.351 ? 0.007 s/op
それに加えて、1対1の比較が本当に意味のあるユースケース。これらのコレクションの両方を使用して、最後に最近使用されたアイテムのキャッシュの実装。現在、skipListの効率性は、より疑わしいイベントのようです。
MyBenchmark.hashMap_put1000_lru avgt 5 0.032 ? 0.001 s/op
MyBenchmark.skipListMap_put1000_lru avgt 5 3.332 ? 0.124 s/op
ここにJMHのコードがあります( java -jar target / benchmarks.jar -bm avgt -f 1 -wi 5 -i 5 -t 1
として実行)
static final int nCycles = 50000;
static final int nRep = 10;
static final int dataSize = nCycles / 4;
static final List<String> data = new ArrayList<>(nCycles);
static final Map<String,String> hmap4get = new ConcurrentHashMap<>(3000, 0.5f, 10);
static final Map<String,String> smap4get = new ConcurrentSkipListMap<>();
static {
// prepare data
List<String> values = new ArrayList<>(dataSize);
for( int i = 0; i < dataSize; i++ ) {
values.add(UUID.randomUUID().toString());
}
// rehash data for all cycles
for( int i = 0; i < nCycles; i++ ) {
data.add(values.get((int)(Math.random() * dataSize)));
}
// rehash data for all cycles
for( int i = 0; i < dataSize; i++ ) {
String value = data.get((int)(Math.random() * dataSize));
hmap4get.put(value, value);
smap4get.put(value, value);
}
}
@Benchmark
public void skipList_put() {
for( int n = 0; n < nRep; n++ ) {
Map<String,String> map = new ConcurrentSkipListMap<>();
for( int i = 0; i < nCycles; i++ ) {
String key = data.get(i);
map.put(key, key);
}
}
}
@Benchmark
public void skipListMap_get() {
for( int n = 0; n < nRep; n++ ) {
for( int i = 0; i < nCycles; i++ ) {
String key = data.get(i);
smap4get.get(key);
}
}
}
@Benchmark
public void hashMap_put() {
for( int n = 0; n < nRep; n++ ) {
Map<String,String> map = new ConcurrentHashMap<>(3000, 0.5f, 10);
for( int i = 0; i < nCycles; i++ ) {
String key = data.get(i);
map.put(key, key);
}
}
}
@Benchmark
public void hasMap_get() {
for( int n = 0; n < nRep; n++ ) {
for( int i = 0; i < nCycles; i++ ) {
String key = data.get(i);
hmap4get.get(key);
}
}
}
@Benchmark
public void skipListMap_put1000_lru() {
int sizeLimit = 1000;
for( int n = 0; n < nRep; n++ ) {
ConcurrentSkipListMap<String,String> map = new ConcurrentSkipListMap<>();
for( int i = 0; i < nCycles; i++ ) {
String key = data.get(i);
String oldValue = map.put(key, key);
if( (oldValue == null) && map.size() > sizeLimit ) {
// not real lru, but i care only about performance here
map.remove(map.firstKey());
}
}
}
}
@Benchmark
public void hashMap_put1000_lru() {
int sizeLimit = 1000;
Queue<String> lru = new ArrayBlockingQueue<>(sizeLimit + 50);
for( int n = 0; n < nRep; n++ ) {
Map<String,String> map = new ConcurrentHashMap<>(3000, 0.5f, 10);
lru.clear();
for( int i = 0; i < nCycles; i++ ) {
String key = data.get(i);
String oldValue = map.put(key, key);
if( (oldValue == null) && lru.size() > sizeLimit ) {
map.remove(lru.poll());
lru.add(key);
}
}
}
}
ConcurrentHashMap:マルチスレッドインデックスベースのget / putが必要な場合、インデックスベースの操作のみがサポートされます。 Get / PutはO(1)です
ConcurrentSkipListMap:キーでソートされた上位/下位nアイテム、最後のエントリを取得、キーでソートされたマップ全体をフェッチ/トラバースするなど、get / put以外の操作。複雑さはO(log(n))です。パフォーマンスはConcurrentHashMapほど優れていません。 SkipListを使用したConcurrentNavigableMapの実装ではありません。
要約するには、単純なgetおよびputだけでなく、ソートされた機能を必要とするマップでさらに操作を行いたい場合にConcurrentSkipListMapを使用します。