Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Red-Black Tree for Ruby

Red-Black Tree for Ruby

Kyuden Masahiro

March 22, 2019
Tweet

More Decks by Kyuden Masahiro

Other Decks in Programming

Transcript

  1. Red-Black Tree for Ruby
    Kyuden Masahiro

    View full-size slide

  2. 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)

    View full-size slide

  3. Ruby biz Grand prix 2017

    View full-size slide

  4. RubyKaigi 2017

    View full-size slide

  5. RubyKaigi 2017
    RubyKaigi 2018

    View full-size slide

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

    View full-size slide

  7. どう実装しましょうか?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  13. 各計算量のおけるステップ数比較
    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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  16. Rbtreeとは?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  23. Left-leaning Red-Black Tree

    View full-size slide

  24. 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の発明によるもの

    View full-size slide

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

    View full-size slide

  26. 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

    View full-size slide

  27. 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

    View full-size slide

  28. 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

    View full-size slide

  29. 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

    View full-size slide

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

    View full-size slide

  31. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  35. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  38. https://github.com/kyuden/llrbtree

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  42. 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)

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    – もし要素数が少ない場合のみの使用を前提とするアプリケーショ
    ンなら実装コストを優先して左傾赤黒木が選択肢になりえる

    View full-size slide

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

    View full-size slide