Slide 1

Slide 1 text

Red-Black Tree for Ruby Kyuden Masahiro

Slide 2

Slide 2 text

I’m kyuden ● Github: kyuden ● Twitter: @kyuden_ ● Banken gem creator ● https://github.com/kyuden/banken ● Sorcery gem commiter ● https://github.com/Sorcery/sorcery ● WEB+DB Press Ruby連載(vol96~101)

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Ruby biz Grand prix 2017

Slide 6

Slide 6 text

Our Team

Slide 7

Slide 7 text

RubyKaigi 2017

Slide 8

Slide 8 text

RubyKaigi 2017 RubyKaigi 2018

Slide 9

Slide 9 text

今回のストーリーの始まり ● インメモリで約800万個の数値(integerのみ)の集合から最小 値を取得したい – 数値は頻繁に追加/削除され集合の数は増減する – Rubyを使用する(Redisは使用しないものとする) ※ 実際はもう少し複雑な要件だったがミニマルにして抜粋

Slide 10

Slide 10 text

どう実装しましょうか?

Slide 11

Slide 11 text

SortedSet ● Rubyの標準ライブラリであるSortedSetを発見 ● SortedSetという名前から今回の用途にぴったりだと確信

Slide 12

Slide 12 text

これで無事何もなければこのスライドは無価値

Slide 13

Slide 13 text

問題発生 ● 想像以上にパフォーマンスがでなかった ※1 – まず自分が書いたプログラムのバグを疑う ● 見当たらない ● プロファイリングしてみる – SortedSetに時間がかかりすぎていた ※1 ベンチマークはのちほど

Slide 14

Slide 14 text

SortedSetの内部実装 ● rbtreeというgemがrequireできる場合は内部記憶として rbtreeを利用する ● ただrbtreeはthird partyのgemでありデフォルトではイン ストールされていない – その場合requireできないので内部記憶はSet同様Hash を利用する – なのでデフォルトの挙動はこちら

Slide 15

Slide 15 text

SortedSetの内部実装 – rbtreeを利用しない場合 - ● SourtedSet#first – SourtedSet#firstが呼ばれると全要素をArray#sort!して いた ● 一度sortしたものはキャッシュするが新しい要素が 追加/削除された場合はキャッシュが消える ● 最悪計算量はO(n^2) ● SortedSetという名前からO(log n)を期待していた

Slide 16

Slide 16 text

各計算量のおけるステップ数比較 log n n n log n n^2 2 5 12 25 3 10 33 100 4 15 59 225 4 20 86 400 5 25 116 625 5 30 147 900 7 100 664 10000 8 300 2468 90000 10 1000 9966 1000000 13 10000 132877 100000000 16 100000 1660964 10000000000 20 1000000 19931568 1000000000000

Slide 17

Slide 17 text

解決? ● とりあえず`gem install rbtree`すればO(log n)が手に入るので今回の問題 は解決する – だがRubyとしてこれで良いのかは少し疑問が残る – 気づかずにO(n^2)のSortedSetを利用してしまう人がいるのではないか – Ruby本体にrbtreeのようなデータ構造の実装が入ると今回の自分のよ うに調査しなくて済むし、気づかなかった人もO(log n)が手に入る – もしくはRuby本体からSortedSetを削除し後方互換性を考えgemとし て提供するのも選択肢としてはあり?

Slide 18

Slide 18 text

SortedSet#first vs SortedSet#first with rbtree Ruby 2.6.1 gcc version 8.1.0 (Ubuntu 8.1.0-5ubuntu1~16.04)

Slide 19

Slide 19 text

Rbtreeとは?

Slide 20

Slide 20 text

Rbtree ● Red-Black Tree(赤黒木)の実装を提供するC extension gem Red-Black Tree ● 平衡二分探索木の一種であり現在も広く利用されているデータ構造 – std::set, std::map (GNU C++ ) – TreeMap, TreeSet (Java) – SortedSet (C# .NET) – etc

Slide 21

Slide 21 text

Red-Black Tree -性質- ● 2分木 ● 各ノードは赤か黒の色を持つ ● 根は黒ノード ● 赤ノードの子は黒ノード ● 全ての葉から根への経路上の 黒ノードは同じ個数 1 2 5 4 6 7 8 3

Slide 22

Slide 22 text

Red-Black Tree -性能- ● 探索、挿入、削除の最悪計算量がO(log n) ● 木の高さが2logn以下 – 最短のパスは全てが黒ノードのパス、最長のパスは赤黒交互に並ぶノードのパス ● 最短の最長のパスの長さは2倍以内に収まる ※ AVL木も探索、挿入、削除の最悪計算量はO(log n)だが赤黒木より厳密な平衡性を保つので 赤黒木より木高さも低く検索は早い。一方、その分挿入、削除のリバランスに時間がかかる ため赤黒木より遅くなる場合がある。 よって頻繁に更新しない辞書のような用途だとAVL木が適しているが、様々な汎用的な用 途を考慮すると赤黒木のほうが適しているケースが多いため様々な言語のライブラリの実 装として選択されているのではないかと推測する

Slide 23

Slide 23 text

Red-Black Tree -ユースケース-優先度付きキュー- ● Linuxカーネル 2.6.23 にマージされたCompletely Fair Scheduler (CFS)も内 部でも仮想実行時間でキーとし優先順序付けされた赤黒木を使用しタスク を選択している。 https://github.com/torvalds/linux/blob/master/kernel/sched/fair.c

Slide 24

Slide 24 text

Red-Black Tree -ユースケース-転置インデックス検索 ● Rbtree#lower_bound/upper_bound/boundを使用すれば、キーを正確に指 定する必要はなく、次の大きいキーや次に小さいキー、有る範囲のキーにあ る要素をO(log n)で取得できる – Hashはキー順に並んでいないので同様の操作を行う場合、全てのキーを 参照する必要がある

Slide 25

Slide 25 text

実装が複雑 Red-Black Treeの優れた性質の代償

Slide 26

Slide 26 text

Left-leaning Red-Black Tree

Slide 27

Slide 27 text

Left-leaning Red-Black Tree ● 2008年にRobert Sedgewickによって発表された論文※1にて発表された赤黒 木の異種(variant) ● もともと赤黒木は1978年にRobert SedgewickとLeonidas J. Guibasによって 発表された※2ことを考えると、相対的には最近発表されたといえ、かつ Robert Sedgewick自身による赤黒木の再発明ということでわくわくする ※1 Robert Sedgewick. Left-leaning Red–Black Trees(2008) http://www.cs.princeton.edu/~rs/talks/LLRB/LLRB.pdf ※2 赤黒木の元になったのは1972年のRudolf Bayerの発明によるもの

Slide 28

Slide 28 text

実装が複雑 Red-Black Treeの優れた性質の代償

Slide 29

Slide 29 text

Left-leaning Red-Black Tree - 要約 - ● Left-leaning Red-Black Tree(以後 左傾赤黒木と記載する)は赤黒木より非常にシンプル に実装でき、挿入、削除においては1/4以下のコード量で実装できる – よって従来の赤黒木の複雑性を解決し、メンテナンス性が向上する ● 赤黒木と左傾赤黒木のパフォーマンス比較に関しては明確にはこの論文で述べられて いない – Robert Sedgewickによるプレゼンテーション資料※1では左傾赤黒木の特徴として 赤黒木と比較して”less code implies faster insert, delete”とパフォーマンスに関し 言及されていた。(だがあくまで”imply”であり計測結果の数値の記載はない) ※1 Robert Sedgewick. Left-leaning Red–Black Trees http://www.cs.princeton.edu/~rs/talks/LLRB/RedBlack.pdf

Slide 30

Slide 30 text

Left-leaning Red-Black Tree - コンセプト - ● 左傾赤黒木は赤黒木に対してさらに制約を追加することで赤黒木の複 雑な条件分岐を減らすことに成功している – 赤黒木を2-3-4木もしくは2-3木に1対1対応させる過程で新しい制 約が生まれ結果それが左傾赤黒木となる 1 2 5 4 6 7 8 3 1 2 3 5 4 6 7 8

Slide 31

Slide 31 text

2-3-4木 -ノードの種類 - b ● 2ノードは、1つの要素と2つの子をもつ ● 3ノードは、2つの要素と3つの子をもつ ● 4ノードは、3つの要素と4つの子をもつ ※子は空の場合もあり a c b d a c e b d f a c e g

Slide 32

Slide 32 text

2-3-4木をRed-Black Treeで表現 -3ノード場合- 5 3 3 5 ● 子ノードに赤ノードを含む赤黒木で表現する – 3ノードの場合は上記のように2種類の赤黒木に分類できる – 必ず左の子に赤ノードが傾くという制約を加え、もし右の子に赤いノードが傾いている場合は 左回転(Left flip)して左の子に赤ノードがくるよう強制する(Left-leaning Red-Black Treeという命 名はこの制約からきたものである) ● その結果、右の子が赤ノードである多くのケースを排除でき赤黒木の分岐条件を減らすこと ができる 3 3 5 5 3 3

Slide 33

Slide 33 text

2-3-4木をRed-Black Treeで表現 -2ノード場合- 4 ● 2ノードの場合は2分木なのでそのまま赤黒木で表現できる 4

Slide 34

Slide 34 text

2-3-4木をRed-Black Treeで表現 -4ノード場合- 8 7 6 7 8 6 7 3 6 8 6 8 7 8 6 7 6 7 8 6 8 7

Slide 35

Slide 35 text

Left-leaning Red-Black Treeを2-3-4木で表現 ● 左傾赤黒木が2-3-4木にみえてくるはず 1 2 5 4 6 7 8 3

Slide 36

Slide 36 text

Left-leaning Red-Black Treeを2-3-4木で表現 ● 左傾赤黒木が2-3-4木にみえてくるはず.. 1 2 5 4 6 7 8 3

Slide 37

Slide 37 text

Left-leaning Red-Black Treeを2-3-4木で表現 ● 左傾赤黒木が2-3-4木にみえてくるはず.... 1 2 5 4 6 7 8 3

Slide 38

Slide 38 text

Left-leaning Red-Black Treeを2-3-4木で表現 ● 左傾赤黒木が2-3-4木にみえてくるはず...... 1 2 5 4 6 7 8 3 6 7 8 3 5 1 2 4

Slide 39

Slide 39 text

Left-leaning Red-Black Treeを2-3-4木で表現 ● 左傾赤黒木が2-3-4木にみえてくるはず! 1 2 3 5 4 6 7 8

Slide 40

Slide 40 text

Left-leaning Red-Black Tree – ここまでのまとめ- ● 赤黒木は赤黒ノードのノードパターンを全てチェックする必 要があったが、2-3-4木を赤黒木で表現する過程で必要となる 制約を加えることで、いくつかのパターンが自明となり チェックする必要なくなったため左傾赤黒木はシンプルな実 装となる ● ただ、4ノードの分割処理など他にもケアする内容があり実 際はもう少し複雑だが、本資料では詳細な左傾赤黒木の解説 は割愛する

Slide 41

Slide 41 text

https://github.com/kyuden/llrbtree

Slide 42

Slide 42 text

kyuden/llrbtree ● 左傾赤黒木を提供するC extention gem ● 左傾赤黒木は`llrbtree/ext/llrbtree/tree.c`に実装 ● binding部分は左傾赤黒木に合わせて改変したが基本的には rbtree gemと同じであるためbinding部分のCopyrightは原 作者のまま ● Robert Sedgewickがさきほどの論文で推奨していた再帰に よる追加、削除処理の実装を選択

Slide 43

Slide 43 text

kyuden/llrbtree -モチベーション- ● 論文には明示的に赤黒木と左傾赤黒木のパフォーマンス比較が記載 されていなかった ● 論文には左傾赤黒木のデメリットの記載が少なかった ● もし、前述した論文やプレゼンテーション資料通りコード量も少な くメンテナブルで、かつ赤黒木より高速にうまく実装できれ ば、Rubyにfeature request送ってみるのもありだと思った – 少なくとも現在のSortedSet#firstより数千倍は高速化できる ● 論文読んで高まった

Slide 44

Slide 44 text

llrbtreeとrbtreeのパフォーマンス比較 -検索- Ruby 2.6.1 gcc version 8.1.0 (Ubuntu 8.1.0-5ubuntu1~16.04)

Slide 45

Slide 45 text

llrbtreeとrbtreeのパフォーマンス比較 -挿入- Ruby 2.6.1 gcc version 8.1.0 (Ubuntu 8.1.0-5ubuntu1~16.04) Ruby 2.6.1 gcc version 8.1.0 (Ubuntu 8.1.0-5ubuntu1~16.04)

Slide 46

Slide 46 text

llrbtreeとrbtreeのパフォーマンス比較 -削除- Ruby 2.6.1 gcc version 8.1.0 (Ubuntu 8.1.0-5ubuntu1~16.04)

Slide 47

Slide 47 text

パフォーマンス結果 -要約- ● 左傾赤黒木は検索、挿入、削除がいずれの操作においても赤黒木より遅い という結果となった – 削除に関しては要素数が100万個の場合約2倍の開きがあった – 左傾赤黒木は赤黒木に比べて挿入と削除、各操作における回転数が挿入 は2.96x、削除は51.99x増えている※1 ● 前述したRobert Sedgewickの論文にパフォーマンス比較がなかったのは左 傾赤黒木が遅いため? ※1 http://www.read.seas.harvard.edu/~kohler/notes/llrb.html

Slide 48

Slide 48 text

Red-Black Tree vs Left-leaning Red-Black Tree ● 左傾赤黒木はたしかにコード量はシンプルだがパフォーマンス比較の 結果をふまえると、例えばプログラミング言語の標準ライブラリとし て提供するなら赤黒木の方がメリットがあるのではないか – 赤黒木の方が歴史が長く安定して実用されてきたという実績から も左傾赤黒木が選ばれるユースケースは限られてくるのではない か – もし要素数が少ない場合のみの使用を前提とするアプリケーショ ンなら実装コストを優先して左傾赤黒木が選択肢になりえる

Slide 49

Slide 49 text

本日の内容 ● SortedSetの内部実装/気をつけるべき点 ● Red-Black Tree – 性質/性能 – ユースケース ● Left-leaning Red-Black Tree – コンセプト – Red-Black Treeとのパフォーマンス比較

Slide 50

Slide 50 text

おわり