スキップリストとバイナリ検索ツリー


回答:


257

スキップリストは、同時アクセス/変更の影響を受けやすくなっています。Herb Sutterは、並行環境でのデータ構造に関する記事を書きました。より詳細な情報があります。

二分探索木で最も頻繁に使用される実装は、赤黒木です。同時に問題が発生するのは、ツリーが変更されると、多くの場合、バランスを調整する必要があるためです。リバランス操作はツリーの大部分に影響を与える可能性があり、ツリーノードの多くでミューテックスロックが必要になります。ノードをスキップリストに挿入することは、はるかにローカライズされており、影響を受けるノードに直接リンクされているノードのみをロックする必要があります。


Jon Harropsコメントからの更新

私はFraserとHarrisの最新の論文「ロックなしの並行プログラミング」を読みました。ロックフリーのデータ構造に興味があるなら、本当に良いものです。このペーパーでは、トランザクションメモリと理論的な操作のマルチワード比較およびスワップMCASに焦点を当てています。これらの両方は、まだハードウェアでサポートされていないため、ソフトウェアでシミュレーションされます。彼らがソフトウェアでMCASを構築することができたことにかなり感銘を受けました。

トランザクショナルメモリはガベージコレクタを必要とするため、特に説得力のあるものは見つかりませんでした。また、ソフトウェアトランザクションメモリはパフォーマンスの問題に悩まされています。ただし、ハードウェアのトランザクションメモリが一般的になれば、非常に興奮します。結局のところ、それはまだ研究中であり、今後10年間は​​本番コードに使用されません。

セクション8.2では、いくつかの並行ツリー実装のパフォーマンスを比較します。調査結果をまとめます。50、53、54ページにいくつかの非常に有益なグラフがあるので、pdfをダウンロードする価値があります。

  • スキップリストのロックはめちゃくちゃ速いです。同時アクセスの数に応じて非常によく拡張されます。これがスキップリストを特別なものにしている理由です。他のロックベースのデータ構造は、プレッシャーのもとで侵入する傾向があります。
  • ロックフリースキップリストは、ロックスキップリストより常に高速ですが、かろうじて高速です。
  • トランザクションスキップリストは、ロックバージョンと非ロックバージョンより常に2〜3倍遅くなります。
  • 赤黒木をロックすると、同時アクセス時に鳴きます。それらのパフォーマンスは、同時ユーザーが増えるごとに直線的に低下します。2つの既知のロック赤黒ツリー実装のうち、1つは本質的にツリーの再調整中にグローバルロックを持っています。もう1つは、派手な(そして複雑な)ロックエスカレーションを使用しますが、グローバルロックバージョンを大幅に実行しません。
  • ロックフリーの赤黒木は存在しません(もう当てはまりません。更新を参照してください)。
  • トランザクションの赤黒ツリーは、トランザクションのスキップリストと同等です。それは非常に驚くべきことであり、非常に有望でした。トランザクションメモリ。ただし、書き込みがはるかに容易な場合は遅くなります。非同時バージョンでのクイック検索と置換と同じくらい簡単です。

更新
ここにロックフリーツリーに関する論文があります:CASを使用したロックフリーの赤黒木
深くは調べていませんが、表面的にはしっかりしているようです。


3
縮退していないスキップリストでは、ノードの約50%に単一のリンクしかなく、挿入と削除が非常に効率的であることは言うまでもありません。
Adisak、

2
リバランスには、ミューテックスロックは必要ありません。cl.cam.ac.uk/research/srg/netos/lock-freeを
Jon Harrop

3
@ジョン、はい、いいえ。既知のロックフリーの赤黒木実装はありません。FraserとHarrisは、トランザクションメモリベースのレッドブラックツリーの実装方法とそのパフォーマンスを示しています。トランザクションメモリはまだ研究領域に非常に多くあります。そのため、プロダクションコードでは、赤黒ツリーはツリーの大部分をロックする必要があります。
deft_code

4
@deft_code:Intelは最近、HaswellでのTSXによるトランザクションメモリの実装を発表しました。これは、あなたが言及したそれらのロックフリーデータ構造に関して興味深いことを証明するかもしれません。
マイクベイリー

2
Fizzの回答は、この回答(2012年)よりも(2015年から)より最新であると思うので、おそらく今のところ好ましい回答になるはずです。
fnl 2017

81

まず、ランダム化されたデータ構造を、最悪の場合の保証を提供するものと公平に比較​​することはできません。

スキップリストは、Dean and Jonesの「Exploring the Duality between Skip Lists and Binary Search Trees」で詳細に説明されているように、ランダムバランスバイナリ検索ツリー(RBST)と同等です。

逆に、最悪の場合のパフォーマンスを保証する確定的なスキップリストを使用することもできます。Munro et al。

上記の主張に反して、並行プログラミングでうまく機能するバイナリサーチツリー(BST)の実装を持つことができます。同時実行に重点を置いたBSTの潜在的な問題は、赤黒(RB)ツリーからの場合と同じように、バランスについて同じことが簡単に得られないことです。(ただし、「標準」、つまりランダム化されたスキップリストでは、これらの保証も得られません。)常にバランスを維持することと、良好な(そしてプログラムが容易な)同時アクセスとの間にはトレードオフがあるため、通常、リラックスした RBツリーが使用されます。優れた並行性が必要な場合。リラクゼーションでは、すぐにツリーのバランスを調整しません。やや日付が付けられた(1998年の)調査については、ハンケの '' The Performance of Concurrent Red-Black Tree Algorithms '' [ps.gz]を参照してください

これらの最近の改良点の1つは、いわゆるクロマティックツリーです(基本的に、黒が1、赤が0になるような重みがありますが、その間の値も許可されます)。そして、クロマチックツリーはスキップリストにどのように対応しますか?ブラウンらが何を見てみましょう。「ノンブロッキングツリーの一般的な手法」(2014)は次のように述べています。

128スレッドで、このアルゴリズムはJavaのノンブロッキングスキップリストより13%から156%優れています。これは、BronsonらのロックベースのAVLツリーです。63〜224%、ソフトウェアトランザクションメモリ(STM)を13〜134倍使用するRBT

追加する編集:フレーザーとハリス(2007)の「ロックなしの並行プログラミング」でベンチマークされたPughのロックベースのスキップリストは、独自のロックフリーバージョンに近づいている(ここのトップアンサーで強く主張されている点)、また、適切な同時操作のために調整されています。Pughの"スキップリストの同時保守"ですが、かなり穏やかな方法です。それにもかかわらず、より新しい/ 2009年の論文「単純な楽観的スキップリストアルゴリズム」(Pughのよりも)おそらく単純なロックベースの同時スキップリストの実装を提案しているHerlihyらによると、十分に説得力のある正確さの証拠を提供していないとしてPughを批判しました。この(あまりにも知識が多すぎる)ことはさておき、Herlihy et al。スキップリストのより単純なロックベースの実装は、実際にはJDKのロックフリー実装と同様にスケーリングに失敗しますが、高競合(50%挿入、50%削除、0%ルックアップ)の場合のみ...どのFraserそして、ハリスはまったくテストしませんでした。FraserとHarrisは、75%のルックアップ、12.5%の挿入、12.5%の削除のみをテストしました(50万要素以下のスキップリストで)。Herlihyらのより単純な実装。また、テストした競合が少ない場合のJDKのロックフリーソリューションに近づきます(70%のルックアップ、20%の挿入、10%の削除)。彼らは、スキップリストを十分に大きく(つまり、200K要素から2M要素に)したときに、このシナリオのロックフリーソリューションを打ち破ったため、ロックの競合の確率は無視できるほどになりました。Herlihy et al。Pughの証明を越えてハングアップを乗り越えて、彼の実装もテストしていましたが、残念ながらそうしませんでした。

EDIT2:すべてのベンチマークの(2015年に公開された)マザーロードを見つけました:Gramoliの「同期について知りたい以上のこと。同期、同時アルゴリズムに対する同期の影響の測定」:この質問に関連する抜粋画像です。

ここに画像の説明を入力してください

「Algo.4」は、上記のブラウン他の前身(2011年以前のバージョン)です。(私は2014年版がどれほど良いか悪いかわかりません)。「Algo.26」は上記のHerlihyの説明です。あなたが見ることができるように、それはアップデートでごまかされ、元の論文のSun CPUよりもここで使用されているIntel CPUではるかに悪い。「Algo.28」は、JDKのConcurrentSkipListMapです。他のCASベースのスキップリスト実装と比較して、期待したほどの効果はありません。高争いの勝者は、「Algo.2」、Crain et al。によって記述されたロックベースのアルゴリズム(!!)です。で「A競合フレンドリーバイナリ検索ツリー」と「Algo.30」から「回転skiplist」である「マルチコアのための対数データ構造」。」。Gramoliは、これらの勝者アルゴリズムに関する3つの論文すべての共著者であることに注意してください。「Algo.27」は、FraserのスキップリストのC ++実装です。

Gramoliの結論は、同様のスキップリストを台無しにするよりも、CASベースの並行ツリー実装を台無しにする方がはるかに簡単であるということです。そして、図に基づいて、それを否定するのは難しいです。この事実についての彼の説明は次のとおりです。

ロックのないツリーを設計することの難しさは、複数の参照をアトミックに変更することの難しさに起因します。スキップリストは、後続ポインタを介して相互にリンクされたタワーで構成され、各ノードはそのすぐ下のノードを指します。各ノードにはサクセサタワーとその下にサクセサがあるため、それらはツリーに類似していると考えられますが、大きな違いは、下向きのポインタが不変であるため、ノードのアトミック変更が簡略化されることです。この違いが、図[上記]に見られるように、スキップリストが激しい競合の下でツリーよりも優れている理由です。

この困難を克服することは、ブラウンらの最近の研究における主要な懸念事項でした。彼らは、 (マシンレベルの)CASを使用して実装されたLLX / SCXと呼ばれるマルチレコードLL / SC複合「プリミティブ」の構築に関する完全に別の(2013)ペーパー「ノンブロッキングデータ構造の実用的なプリミティブ」を持っています。ブラウン等。このLLX / SCXビルディングブロックを2014年(2011年ではなく)の並行ツリー実装で使用しました。

ここでは、「ホットスポットなし」/コンテンションフレンドリー(CF)スキップリストの基本的なアイデアを要約する価値があると思います。。これは、リラックスしたRBツリー(および同様の並行性データ構造)からの本質的なアイデアを採用しています。タワーは、挿入時にすぐに構築されるのではなく、競合がなくなるまで遅延されます。逆に、高い塔を削除すると、多くの競合が発生する可能性があります。これは、Pughの1990年の同時スキップリストペーパーまでさかのぼります。そのため、Pughは削除時にポインターの反転を導入しました(スキップリストに関するWikipediaのページでは、今日もなお言及されていません)。CFスキップリストはこれをさらに一歩進め、高い塔の上のレベルの削除を遅らせます。CFスキップリストの両方の種類の遅延操作は、(CASベースの)別のガベージコレクターのようなスレッドによって実行されます。このスレッドは、その作成者が「適応スレッド」と呼んでいます。

Synchrobenchコード(テストされたすべてのアルゴリズムを含む)は、https//github.com/gramoli/synchrobenchで入手できます。最新のブラウンら。実装(上記には含まれていません)はhttp://www.cs.toronto.edu/~tabrown/chromatic/ConcurrentChromaticTreeMap.javaで入手できます。32+コアマシンを利用できる人はいますか?J / K私のポイントは、これらを自分で実行できることです。


12

また、与えられた答えに加えて(バランスの取れたツリーに匹敵するパフォーマンスと組み合わせた実装の容易さ)。スキップリストには、実装内にリンクリストが事実上含まれているため、順序どおりのトラバーサル(順方向および逆方向)を実装する方がはるかに簡単です。


1
「def func(node):func(left(node)); op(node); func(right(node))」のように単純なビンツリーの順序トラバーサルではありませんか?
Claudiu、

6
もちろん、1回の関数呼び出しですべてをトラバースしたい場合はtrueです。しかし、std :: mapのようにイテレータスタイルのトラバーサルが必要な場合は、はるかに煩わしくなります。
エヴァンテラン

@Evan:CPSで記述できる関数型言語ではありません。
Jon Harrop、

@エヴァン:def iterate(node): for child in iterate(left(node)): yield child; yield node; for child in iterate(right(node)): yield child;?=)。非ローカルコントロールiz awesom .. @ジョン:CPSで書くのは面倒ですが、継続を意味するのでしょうか?ジェネレーターは基本的にPythonの継続の特別なケースです。
Claudiu

1
@Evan:はい、変更中にノードパラメータがツリーから切り取られる限り機能します。C ++トラバーサルにも同じ制約があります。
deft_code

10

実際には、プロジェクトでのBツリーのパフォーマンスは、スキップリストよりも優れていることがわかりました。スキップリストは、理解しやすいように見えるが、Bツリーを実装することはないんという難しいです。

私が知っている1つの利点は、一部の賢い人々が、アトミック操作のみを使用するロックフリーの同時スキップリストを実装する方法を考え出したことです。たとえば、Java 6にはConcurrentSkipListMapクラスが含まれており、頭がおかしい場合は、ソースコードを読み込むことができます。

しかし、同時Bツリーバリアントを記述することもそれほど難しくありません-私はそれを他の誰かが行ったのを見てきました-ツリーに沿って歩くときに「念のため」先制的にノードを分割してマージする場合、あなたはする必要はありませんデッドロックを心配し、ツリーの2つのレベルで一度にロックを保持する必要があるだけです。同期オーバーヘッドは少し高くなりますが、Bツリーの方がおそらく高速です。


4
Binary TreeをBツリーと呼んではいけないと思います。その名前のDSは完全に異なります
Shihab Shahriar Khan 2017

8

あなたが引用したウィキペディアの記事から:

n(n)操作(昇順ですべてのノードにアクセスすることを強制する(リスト全体を印刷するなど))は、最適な方法でスキップリストのレベル構造の舞台裏のランダム化を実行する機会を提供します。スキップリストをO(log n)検索時間にします。[...]最近実行していない[そのような]Θ(n)操作のスキップリストは、常に可能であるため、従来の平衡型ツリーデータ構造と同じ絶対的な最悪の場合のパフォーマンス保証を提供しません(確率は非常に低いものの)、スキップリストの作成に使用されたコインフリップにより、バランスの悪い構造が生成される

編集:だからトレードオフです:スキップリストは、不均衡なツリーに退化するリスクがあるため、使用するメモリが少なくなります。


これは、スキップリストを使用しない理由です。
Claudiu

7
MSDNを引用すると、「確率[100レベル1要素の場合]は正確に1,267,650,600,228,229,401,496,703,205,376に1」です。
peterchen 2008年

8
メモリの使用量が少ないと言うのはなぜですか?
ジョナサン

1
@peterchen:なるほど、ありがとう。それで、これは確定的なスキップリストでは発生しませんか?@ミッチ:「スキップリストはより少ないメモリを使用する」。スキップリストは、バランスのとれたバイナリツリーよりも少ないメモリをどのように使用しますか?各ノードに4つのポインターがあり、ノードが重複しているように見えますが、ツリーには2つのポインターしかなく、重複はありません。
Jon Harrop、

1
@Jon Harrop:レベル1のノードは、ノードごとに1つのポインターのみを必要とします。より高いレベルのノードは、ノードごとに2つのポインター(1つは次のノードへ、もう1つはその下のレベルへ)を必要としますが、もちろんレベル3ノードは、その1つの値に対して合計5つのポインターを使用していることを意味します。もちろん、これはまだ多くのメモリを消費します(役に立たないスキップリストが必要で、大きなデータセットがある場合は、バイナリ検索よりも)...しかし、何かが足りないと思います...
Brian

2

スキップリストはリストを使用して実装されます。

ロックフリーソリューションは、1つまたは2つのリンクリストに存在しますが、O(logn)データ構造にCASのみを直接使用するロックフリーソリューションはありません。

ただし、CASベースのリストを使用してスキップリストを作成できます。

(CASを使用して作成されたMCASは、任意のデータ構造を許可し、概念実証の赤黒木はMCASを使用して作成されていたことに注意してください)。

それで、奇妙なことに、それらは非常に便利であることがわかります:-)


5
「O(logn)データ構造にCASのみを直接使用するロックフリーソリューションはありません。」違います。カウンターの例については、cl.cam.ac.uk / research / srg / netos / lock
Jon Harrop

-1

スキップリストには、ロックストリッピングの利点があります。ただし、ランタイムは新しいノードのレベルがどのように決定されるかによって異なります。通常、これはRandom()を使用して行われます。56000語の辞書では、スキップリストはスプレイツリーよりも時間がかかり、ツリーはハッシュテーブルよりも時間がかかりました。最初の2つはハッシュテーブルのランタイムと一致しませんでした。また、ハッシュテーブルの配列も同時にロック解除できます。

参照の局所性が必要な場合は、スキップリストと同様の順序付きリストが使用されます。例:アプリケーションで日付の前後のフライトを検索する。

インメモリバイナリ検索スプレイツリーは優れており、頻繁に使用されます。

スキップリストとスプレイツリー対ハッシュテーブルランタイムの辞書検索操作


私は簡単に見て、あなたの結果はSplayTreeよりも速くSkipListを示しているようです。
Chinasaur 2013

スキップリストの一部としてランダム化を想定することは誤解を招きます。要素をスキップする方法は重要です。確率構造にランダム化が追加されました。
user568109 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.