ConcurrentHashMapのパフォーマンスは、HashMapと比較して、特に.get()操作と比較してどうですか(0〜5000の範囲で、アイテムが少ない場合に特に興味があります)。
HashMapの代わりにConcurrentHashMapを使用しない理由はありますか?
(null値は許可されないことを知っています)
更新
明確にするために、実際の同時アクセスの場合のパフォーマンスは明らかに低下しますが、同時アクセスがない場合のパフォーマンスをどのように比較しますか?
このトピックが非常に古く、まだこの件に関するテストを誰も提供していないことに驚いた。 ScalaMeter
を使用して、2つのシナリオでadd
とget
の両方に対してremove
、HashMap
およびConcurrentHashMap
のテストを作成しました。 :
HashMap
はスレッドセーフではないため、スレッドごとに個別のHashMap
を作成しましたが、共有ConcurrentHashMap
を使用しました。コードは利用可能です 私のレポで 。
可能な限り高速にデータを操作する場合は、利用可能なすべてのスレッドを使用します。それは明らかなようです、各スレッドにはやるべきことの全作業の1/nがあります。
シングルスレッドアクセスを選択する場合は、HashMap
を使用します。これは単に高速です。 add
メソッドの場合、3倍も効率的です。 get
ではConcurrentHashMap
のみが高速ですが、それほど高速ではありません。
多くのスレッドでConcurrentHashMap
を操作する場合、スレッドごとに別々のHashMaps
を操作するのと同様に効果的です。そのため、データを異なる構造に分割する必要はありません。
要約すると、ConcurrentHashMap
のパフォーマンスはシングルスレッドで使用する場合は低下しますが、作業を行うためにスレッドを追加するとプロセスが確実にスピードアップします。
プラットフォームのテスト
AMD FX6100、16GBラム
Xubuntu 16.04、Oracle JDK 8 update 91、Scala 2.11.8
スレッドセーフは複雑な問題です。オブジェクトをスレッドセーフにしたい場合は、意識的に行い、その選択を文書化します。クラスを使用する人は、使用を簡単にするときにスレッドセーフであることに感謝しますが、かつてスレッドセーフであったオブジェクトが将来のバージョンでそうではなくなった場合、あなたを呪います。スレッドセーフティは、本当にすてきですが、クリスマスだけのものではありません!
だから今あなたの質問に:
ConcurrentHashMap(少なくとも Sunの現在の実装 で)は、基礎となるマップをいくつかの個別のバケットに分割することで機能します。要素を取得すること自体はロックを必要としませんが、アトミック/揮発性の操作を使用します。これは、メモリバリア(潜在的に非常にコストが高く、他の可能な最適化を妨げる)を意味します。
シングルスレッドの場合、JITコンパイラによってアトミック操作のすべてのオーバーヘッドを排除できる場合でも、どのバケットを調べるかを決定するオーバーヘッドがあります-確かに、これは比較的迅速な計算ですが、それでも排除することは不可能です。
使用する実装を決定する場合、選択はおそらく簡単です。
これが静的フィールドである場合、テストでこれが実際のパフォーマンスキラーであることが示されない限り、ほぼ確実にConcurrentHashMapを使用する必要があります。クラスには、そのクラスのインスタンスとは異なるスレッドセーフティ期待値があります。
これがローカル変数である場合、HashMapで十分である可能性が高い-オブジェクトへの参照が別のスレッドにリークする可能性があることがわかっている場合を除きます。 Mapインターフェイスにコーディングすることにより、問題を発見した場合に後で簡単に変更できるようになります。
これがインスタンスフィールドであり、クラスがスレッドセーフになるように設計されていない場合は、スレッドセーフではないことをドキュメント化し、HashMapを使用します。
このインスタンスフィールドがクラスがスレッドセーフではない唯一の理由であり、スレッドセーフが約束する制限に耐える意思があることがわかっている場合は、テストでパフォーマンスに大きな影響がない限り、ConcurrentHashMapを使用します。その場合、クラスのユーザーが何らかの方法でオブジェクトのスレッドセーフバージョンを選択できるようにすることを検討します。これには、おそらく別のファクトリメソッドを使用します。
いずれの場合も、クラスをスレッドセーフ(または条件付きスレッドセーフ)として文書化して、クラスを使用する人が複数のスレッドでオブジェクトを使用できることを認識し、クラスを編集する人が将来スレッドセーフを維持する必要があることを認識します。
(1つの理由により)may格納している特定のオブジェクトのハッシュ分布にある程度依存しているため、測定することをお勧めします。
標準のハッシュマップは並行性保護を提供しませんが、並行ハッシュマップは提供します。使用可能になる前は、ハッシュマップをラップしてスレッドセーフアクセスを取得できましたが、これは粗粒度ロックであり、すべての同時アクセスがシリアル化されるため、パフォーマンスに実際に影響を与える可能性がありました。
並行ハッシュマップはロックストリッピングを使用し、特定のロックの影響を受けるアイテムのみをロックします。ホットスポットなどの最新のvmで実行している場合、vmは可能であればロックバイアス、粗密化、および省略を使用するため、実際に必要な場合にのみロックにペナルティを支払います。
要約すると、並行スレッドがマップにアクセスし、その状態の一貫したビューを保証する必要がある場合、並行ハッシュマップを使用します。
テーブル全体に10個のロックを使用する1000要素のハッシュテーブルの場合、10000スレッドが挿入され、10000スレッドがそこから削除される時間の半分近くを節約できます。
興味深い実行時間の違いは here です
常に同時データ構造を使用します。ただし、ストライピングの欠点(後述)が頻繁に発生する場合を除きます。その場合、すべてのロックを取得する必要がありますか?これを行う最善の方法は再帰によることだと読みました。
ロックストライピングは、データの整合性を損なうことなく、競合の多いロックを複数のロックに分割する方法がある場合に役立ちます。これが可能かどうかは、何らかの考慮が必要であり、常にそうとは限りません。データ構造も決定の要因です。したがって、ハッシュテーブルの実装に大きな配列を使用する場合、ハッシュテーブル全体に対して単一のロックを使用して同期することにより、スレッドがデータ構造に順次アクセスすることになります。これがハッシュテーブルの同じ場所である場合、それは必要ですが、テーブルの2つの極端にアクセスしている場合はどうでしょう。
ロックストライピングの欠点は、ストライピングの影響を受けるデータ構造の状態を取得することが難しいことです。この例では、すべてのストライプロックを取得する必要があるため、テーブルのサイズ、またはテーブル全体をリスト/列挙しようとすると面倒な場合があります。
ここでどんな答えを期待していますか?
明らかに依存します書き込みと同時に発生する読み取りの数と書き込みおよび法線マップの長さアプリの書き込み操作で「ロック」されます(putIfAbsent
でConcurrentMap
メソッドを使用するかどうか)。ベンチマークはほとんど意味がありません。
何を意味するのかは明確ではありません。スレッドセーフを必要とする場合、選択肢はほとんどありません。ConcurrentHashMapのみです。また、get()呼び出しにはパフォーマンス/メモリペナルティがあります。不幸な場合はvolatile変数にアクセスしてロックします。