冬のLock free祭り safe

18,455 views
18,252 views

Published on

Published in: Technology
1 Comment
57 Likes
Statistics
Notes
No Downloads
Views
Total views
18,455
On SlideShare
0
From Embeds
0
Number of Embeds
6,861
Actions
Shares
0
Downloads
122
Comments
1
Likes
57
Embeds 0
No embeds

No notes for slide

冬のLock free祭り safe

  1. 1. DSIRNLP@kumagi
  2. 2. 辻Lock-freeLock-freeと発言した人に文脈を無視していきなり@を飛ばす行為
  3. 3. 僕と一緒にLock-free!
  4. 4. CPUの系譜のおさらい無限に続くかに思われたCPU加速戦争周波数が勝手に上がるので「システムを高速化して欲しいという案件は半年遊んでからマシン買い換えさせればいいや」とまで言われた周波数加速はPentium終盤から雲行きが怪しくなるAthlonX2やCore世代の台頭AMD「周波数あげるの無理だからコア数増やそうぜ」
  5. 5. CPUの系譜のおさらいCPUの製造コストのためチップ面積を節約したい尐ない面積で高い性能を出す方法はないか?ポラックの法則「2倍のシングルスレッド性能を得るためには4倍のリソースが必要になる」というintelの中の人の経験則逆手に取れば「半分の性能で良ければ4個のコアを積める」
  6. 6. ポラックの法則半分の性能のコアなら1/4の面積¼の面積なら同じ面積に4個積める!½の性能 × 4コア = 2倍の性能!! 1/2 1/2 1/2 1/2
  7. 7. それ活かしてパワフルなの作れば?1/3の性能なら1/9の面積だから9個積める。それなら合計で3倍のパワーが出る既にあります
  8. 8. Intel Many Core驚きの48コア搭載最大消費電力125W同規模のCPUの約3倍のエネルギー効率今年の秋ごろから世界中の研究機関に試作品が送られて使い方を研究してる知ってる範囲だと東大と京大には納入されてる
  9. 9. なんで普及しないの?
  10. 10. We are the Bottleneck!情報工学の発展が追い付いていない!ヽ(ω)ノ三ヽ(ω)ノもうしわけねぇもうしわけねぇ
  11. 11. マルチコアを使い倒してスーパープログラマへ!!
  12. 12. そしてタダ飯の時代よ今再び!
  13. 13. マルチスレッドは簡単じゃない同時に実行するということは、発生する実行の組み合わせが指数的に爆発する通常はロックを用いて調停を行うそしてBlockingする
  14. 14. What is Blocking?ビーチフラッグを例に CPU
  15. 15. What is Blocking?排他すると ロッ ク OK! クリティカルセク ション
  16. 16. What is Blocking?クリティカルセクションは危険がいっぱい クリティカルセク ション
  17. 17. What is Blocking?クリティカルセクションは危険がいっぱい クリティカルセク ション
  18. 18. What is Blocking? コアが増えるほどに問題が顕著に! 早くしろよ… 早くしろよ…早くしろよ… 早くしろよ…早くしろよ… 早くしろよ…早くしろよ… 早くしろよ… 早くしろよ… クリティカルセク ション
  19. 19. そこで Lock-free クリティカルセクションを 作らないので 他のスレッドを足止めしない!
  20. 20. Lockを用いないとどうなるかCPU内部では処理は複数のステップで行われる 1.xを読み出す ++x; 2.読んだ値に +1 3.xを保存する
  21. 21. Lockを用いないとどうなるか複数スレッドが同時に行うと x==1 スレッド スレッド A B1.xを読み出す(1) 1.xを読み出す(2)2.読んだ値に +1 2.読んだ値に +13.xを保存する(1) 3.xを保存する(3) OK! x==3
  22. 22. Lockを用いないとどうなるか複数スレッドが同時に行うと破綻する場合が x==1ある スレッド スレッド A B1.xを読み出す(1) 1.xを読み出す(1)2.読んだ値に +1 2.読んだ値に +13.xを保存する(2) 3.xを保存する(2) 数が合わない x==2
  23. 23. 道具の紹介「targetが期待通りの値だったら置換」を一気に行う命令以後ではCASと略します bool compare_and_swap(int* target, int expected, int newval){ if(*target == expected){ *target = newval; return true; } return false; }
  24. 24. CASを使ってみよう Lock-free共有カウンタの例int x;void add1(){ int old,new; do{ old = x; new = old+1; 失敗してたらやり直す }while(!cas(&x, old, new));}
  25. 25. CASを使ってみようCASのお陰で衝突しても破綻しない x==1 スレッド スレッド A B1. xを読み出す(1) 1. xを読み出す(1)2. 読んだ値に +13. 値が1なら2へCAS 2. 読んだ値に +14. 失敗したので再挑戦5. xを読み出す(2) 3. 値が1なら2へCAS6. 読んだ値に +17. 値が2なら3へCAS 数が合う! x==3
  26. 26. 冬のLock-free祭りマルチスレッドReadyでありながらロックを用いないデータ構造話す予定のおしながき Lock-free Stack Lock-free Queue Lock-free List Lock-free Hashmap Lock-free SkipList Lock-free Btree Dynamic STM(Obstruction-free STM) ???
  27. 27. Lock-free StackCompare And Swapの正しい使い方
  28. 28. Lock-free Stack ↓ポインタ A Head CAS「Headが指している物を指したノードを作って CAS」
  29. 29. Lock-free Stack CAS CAS CAS Head A B C D 失敗した!
  30. 30. Lock-free Stack A CAS Head CAS B C Dまた失敗した!
  31. 31. Lock-free Stack A Head CAS C B D
  32. 32. Lock-free Stackからpop A Head CAS C D B
  33. 33. ね。簡単でしょ!Lock-free怖くない!いわゆるABA問題は今日は扱いませんどしどし行きます!
  34. 34. Lock-free QUEUE 不変条件に手を加える
  35. 35. Lock-free Queue の Deque Tail Head
  36. 36. Lock-free Queue の DequeDeque操作は簡単Lock-free Stackと同じ挙動なので
  37. 37. Lock-free Queue の Deque Tail Head CAS CAS
  38. 38. Lock-free Queue の EnqueEnque操作1. Enqueしたい要素eを用意する2. 末尾のノードが指すポインタがeを指すようCAS3. Tailポインタがeを指すようCAS 2ステップ必 要
  39. 39. Lock-free Queue の Enque Tail Head CAS CAS
  40. 40. Lock-free Queue の Enque Tail Head CAS 構造が破綻 CASCAS CPU1 CPU2 1. 要素eを用意する 1. 要素eを用意する 2. 末尾をCAS 2. 末尾をCAS 3. TailポインタをCAS 3. TailポインタをCAS
  41. 41. 不変条件を考えるアルゴリズムは必ず何かしらの前提が必要マルチスレッドプログラムでもそれは同じ常にその条件を満たし続けるのは無理→ロック 条件守ってる! 条件守ってる! 時間軸→ 条件守ってない!
  42. 42. 不変条件を守る 他のCPUが思いもよらない変更を加えてくる 危険な瞬間CPU1CPU2CPU3CPU4 時間軸→
  43. 43. 不変条件を守る ロックを用いて排他するCPU1CPU2CPU3CPU4 時間軸→
  44. 44. 不変条件を守る ロックのおかげで危険な瞬間がなくなるCPU1CPU2CPU3CPU4 時間軸→
  45. 45. ロックの目的と効果普通にプログラムを書くとどうしても不変条件を破壊する瞬間が生まれてしまうそこはロックで守るのが普通 void queue::enque(const T& v){ ↓時間軸 node* const new_node = new node(v); node* const tail = que_tail_; tail->next = new_node; que_tail = new_node; } 危ない
  46. 46. ロックの目的と効果 Lock-free Stackは不変条件を満たさない瞬間 を外部から観測されないように最小化して CASで片づけられたCPU1 CAS CASCPU2 CAS CASCPU3 CASCPU4 CAS CAS
  47. 47. ロックの目的と効果 Lock-free Queueは2度CASが要るのでタイミ ングによって危険 危険な瞬間CPU1 CAS CAS CAS CASCPU2 CAS CAS CAS CASCPU3 CAS CASCPU4 CAS CAS CAS CAS
  48. 48. Lock-free Queueはどうするのか 不変条件 Headがキューの先頭を指す Tailがキューの末尾を指す Tail以外のノードは必ず有効なノードを指す 条件を緩め 不変条件 る Headがキューの先頭を指す Tailがキューの末尾を指さないかもしれない Tail以外のノードは必ず有効なノードを指す
  49. 49. Tailが末尾を指さないとは?末尾を指してる Tail Head末尾を指してない Tail Head
  50. 50. 解決「Tailが遅れてたらみんな手伝ってあげてね」「Tailの更新に失敗してもみんな気にしないでね」 Tail Head CAS 破綻しない! CAS CPU1 CPU2 1. 要素eを用意する 1. 要素eを用意する 2. Tailが遅れてたら進める 2. Tailが遅れてたら進める 3. 末尾をCAS 3. 末尾をCAS 4. TailポインタをCAS(失 4. TailポインタをCAS 敗)
  51. 51. Lock-free Queueかっこいい! EnqueとDequeの共同作業! T deque(){ while(1){ const Node* const first = mHead;void enque(const T& v){ const Node* const last = mTail; const Node* const node = new Node(v); const Node* const next = first->mNext; while(1){ if(first != mHead){ continue;} const Node* const last = mTail; if(first == last){ const Node* const next = last->mNext; if(next == NULL){ if(last != mTail){ continue; } while(mHead->mNext == if(next == NULL){ NULL){ usleep(1); } if(compare_and_set(&last- continue;>mNext, next, node)){ } compare_and_set(&mTail, last, node); compare_and_set(&mTail,last,next); return; }else{ } T result = next->mValue; }else{ if(compare_and_set(&mHead, first, next)){ compare_and_set(&mTail, last, next); delete first; } return result; } }} } }
  52. 52. Lock-free Queueかっこいい! EnqueとDequeの共同作業! T deque(){ while(1){ const Node* const first = mHead;void enque(const T& v){ const Node* const last = mTail; const Node* const node = new Node(v); const Node* const next = first->mNext; while(1){ if(first != mHead){ continue;} const Node* const last = mTail; if(first == last){ const Node* const next = last->mNext; if(next == NULL){ if(last != mTail){ continue; } while(mHead->mNext == if(next == NULL){ NULL){ usleep(1); } if(compare_and_set(&last->mNext, next, continue;node)){ } compare_and_set(&mTail, last, node); compare_and_set(&mTail,last,next); return; }else{ } T result = next->mValue; }else{ if(compare_and_set(&mHead, first, next)){ compare_and_set(&mTail, last, next); delete first; } return result; } }} } }
  53. 53. 閑話休題Lock-freeよくある質問と答えそんな複雑な事しなくてもMessage-Passingなら完璧だよ
  54. 54. MessagePassing「メッセージを渡す」という哲学に基づいて、操作の実行順序をメッセージ順にしてしまうことですよね? Deq Enq Deq Deq Enq Queue
  55. 55. Lock-free例えるならこんな感じ! Deq Enq Deq Queue Deq Enq
  56. 56. どういうこと?スケールする! FASTER Single-lock Lock-free
  57. 57. あなたも一緒にLock-free!
  58. 58. 発展話題今のアルゴリズムで実装されてるのがboost.lockfreeのqueueリソース管理がもう尐し複雑IntelのTBBやらFastFlowとかいうライブラリでもこのlock-free queueだったような…このアルゴリズムは発明者の名前を取ってMS-Queueと呼ぶ
  59. 59. Lock-free Listポインタに情報を詰め込め
  60. 60. ConcurrentList複数のスレッドから同時に読み書きできるListいろんな粒度のロックがあるので順番に紹介
  61. 61. 粗粒度ロック全体を排他するだけもちろんスケールしない Head
  62. 62. 悲観的ロックノードごとにロックを用意 ロック・アンロックしながら「歩く」 Head• かなり遅くなる• 他のスレッドのイテレーションを追い越せない
  63. 63. 楽観的ロック必要になるまでロックを取らない Head これでいいんじゃない?
  64. 64. 楽観的ロックだめなんです ロックを2つ取っ 私の安定性…低すぎ… おりゃ! て これ消すぞー! Head Segmentation Fault これ消すぞー 消したぞー
  65. 65. 楽観的ロックロックした後に、リストの先頭からきちんと到達可能であることを確かめてから消す必要があるつまり安全のためリストを2回舐めることになる Head
  66. 66. つまり単一ロックだとスケールしない細粒度ロックだと悲観的にロックすると遅い楽観的にロックすると二度見のコストがつく Lazyな消去を導入する事で改善可能だけど省略
  67. 67. さぁLock-freeだ
  68. 68. 挿入処理• リストの繋ぎ替え処理にCASを使用して衝突を回避 し、検索の邪魔をしない• 道路封鎖せず道路工事してる感じ CAS
  69. 69. 挿入処理CASを使う事によって、同一の場所に同時に複数の挿入が発生しても 大丈夫!CASCASCAS 失敗した!
  70. 70. 削除処理挿入処理と同様に、ポインタをCASで繋ぎ変える CAS追い出しに成功したスレッドがdelete
  71. 71. このアルゴリズムにはミスがある
  72. 72. 問題が連続したノードを同時に削除しようとするとデータ構造が破壊される CAS CAS Segmentation Fault
  73. 73. 問題が削除と挿入がぶつかっても破壊される CAS CAS Memory Leak
  74. 74. 解決策ノードの削除を「マーキング」「ポインタ繋ぎかえ」の2段階に分けるマーキングされてたら誰でも削除を手伝うポインタ繋ぎかえに失敗しても気にしないマーキング前と後の2状態に対して操作を記述
  75. 75. マーキングはどこに?ポインタの1bit目をマーキング対象に 動的確保したメモリは4の倍数ぐらいにはAlignされてる つまり1bit目は通常0 マーキング処理もCASを用いる ココ! 順序づけ大事なぜそんなところに埋め込むの?1回のCASで1word分しか操作できない何とかして削除マークとポインタをAtomicにしたい
  76. 76. 正しい削除手順2回CASを使う CAS CAS
  77. 77. さっきのはどうなるか CAS CAS CASCAS CAS マーキングにより CAS失敗する
  78. 78. さっきのはどうなるか CAS CAS マーキングによりCAS CAS失敗する
  79. 79. 素晴らしい!!
  80. 80. Lock-free Hashmapバケットがアイテムの間を動く
  81. 81. Hashmap便利ですよねいわゆる連想配列O(1)でアクセスできるみんなの人気者
  82. 82. 細粒度Lock Hashmap 0 1 2 3 4 5 6 7全部のバケットにロックを取り付けてやる方法実はあんまり効率よくない
  83. 83. Striped Lock Hashmap 0 1 2 3 4 5 6 7固定数のLockをmoduloでバケットに割り当てるスレッドの数が膨大にならない限りロックそのものが増えても恩恵がないためバケットごとに対応するロックが縞模様になるのでStriped
  84. 84. java.util.concurrent.ConcurrentHashMap 0 1 2 3 4 5 6 7 volatile final基本的にStriped Lockだけど、成功する検索はwait-free失敗する検索はロックを取ってもう一回行う削除はチェインを部分的にまるっとRCUバケットの拡張は再帰的にロックを全て獲得しながら
  85. 85. Lock free?
  86. 86. Lockの無くし方を考える 0 1 2 3 4 5 6 7線形リスト部分はLock-free Listを使えばいいバケット部分の拡張が鬼門
  87. 87. どうするバケット拡張あらかじめ充分巨大なバケットにしてしまう 0富豪的過ぎて実用的でない 1 2CASでバケット列を複製 0 3 1 4複製作業中に挿入・削除が走る 2 5 3 6 一貫性無理☆ 4 7 5 6 7 8 9 10 11
  88. 88. バケットの拡張が 困難
  89. 89. そうだバケットの中身の移動が必要ない設計になればいいんだ!
  90. 90. 全体図内部を昇順のLock-free List一本で集約番兵ノード 00000010 00000001 00000011データノード ↓ ↓ ↓ 01000000 10000000 11000000 00 02 40 48 6d 7f 80 8a c0 74 0 1 Hashmapのインデックス値を逆順にしてリ 2 ストに投入 3
  91. 91. どういうこと?普通はバケットにデータを吊るしてる 0 02 1 48 6d 7f 2 8a 3 74こっちはデータの間にバケットの目印を吊るす 00 02 1本のList 0 40 48 6d 7f 1 2 80 8a 3 c0 74
  92. 92. データの挿入1. 対象となるデータのハッシュ値を算出→22. ハッシュ値に対応するテーブルにアクセス3. テーブルに番兵ノードへのポインタが書いてあるた め対応するノードへジャンプ4. 番兵ノードの指すポインタをたどっていけばHash値 の昇順にデータが並んでいるため、対応する場所に 挿入5. リストが昇順に並んでいるという前提は崩れない! 00 02 40 48 6d 7f 80 8a c0 74 0 169 2 3
  93. 93. テーブルの拡張 番兵ノードの間に挟まるアイテムの数が一定数を超 えた場合にテーブルを拡張する 左のテーブルは工夫してあり、基本的に初期化以降 immutable 詳細な部分は論文で 00 02 40 48 6d 7f 80 8a c0 7401234567
  94. 94. テーブルの拡張 1. 拡張操作はテーブルを大きくするだけでおしまい(CASでも可 能) 2. テーブル拡張後は新規探索は新しいテーブル上で行う 3. テーブルが未初期化ならその一個左の番兵ノードから探索 4. 番兵ノードが挿入されているべき個所を見つけ次第、番兵 ノードを挿入する 5. 目的の場所を見つけたら挿入 6. リストが昇順に並んでいるという前提は崩れない 00 02 40 48 60 6d 69 7f 80 8a c0 74 0 1 2 3 4 562 6 7
  95. 95. ロックなしで並行動作する!何があってもリストが昇順に並んでいるという前提は 崩れない 挿入と拡張が同時に走っても 削除と挿入が同時に走っても 大丈夫!
  96. 96. FAST 驚異的なスケーラビリティ
  97. 97. Lock-free java.util.concurrent.ConcurrentHashMapFAST 性能負けてる!!! スレッド数
  98. 98. Lock-free Hashmapかわいい!
  99. 99. Lock-free SkipList線形化ポイントはどこ?
  100. 100. SkipListご存じ・・・ですよね?
  101. 101. SkipList順序関係のあるデータをO(log n)で検索・挿入・削除ができるデータ構造 2分木とモロ被り乱数に依存するため木と違いリバランスする必要が無い 平衡2分木を平衡に扱うにはリバランス専用のスレッドを 別に立てる方法が現時点で一番高いスループットの出る 方法だったような…LevelDBの中で使われていてちょっと話題に
  102. 102. SkipList昇順に並べた普通のリストに高レベルなリストを加えたものレベル1上がる毎に1/2のノードが間引かれる-∞ 33 ∞-∞ 12 33 ∞-∞ 12 33 64 ∞-∞ 3 12 33 51 64 ∞-∞ 3 8 12 16 33 51 64 ∞-∞ 3 8 12 16 29 33 51 64 ∞
  103. 103. SkipListでの検索高いレベルから順に辿っていくだけ 新幹線・特急・快速・鈍行と乗り換えるイメージ29どこー?-∞ 33 ∞-∞ 12 33 ∞-∞ 12 33 64 ∞-∞ 3 12 33 51 64 あったー! ∞-∞ 3 8 12 16 33 51 64 ∞-∞ 3 8 12 16 29 33 51 64 ∞
  104. 104. これをどうLock-freeにするの?まずはLock-basedなSkip Listの実装を見ましょう
  105. 105. Lock-based Skip Listノードごとにロックを用意
  106. 106. Lock-based Skip List1. ここに挿入したいとする2. そこ以前のリンクの状態を記憶3. 昇順にロックを獲得4. 2の時に記憶した内容と現在の内容が一致していることを確認5. 全部一致したら下から順にリンクを繋ぎ変えていく
  107. 107. Lock-based Skip Listできました!実際は線形化ポイントを確定するためFullyLinkedビットを用いて線形化します 詳しくはgithub.com/kumagi/sl
  108. 108. さあLock-freeだ
  109. 109. 具体的な戦略Lock-free Listと同様に、ポインタに削除bitを導入一番下のリストへの操作をSkipListへの操作と見なすそれ以上のリストは高速化のための「飾り」
  110. 110. Lock-free SkipListへの挿入1. 最下位リストへの挿入を行う2. 下のレベルから順に上位リストのリンクを繋ぎ変える3. 繋ぎ変える途中で他人に書き換えられてたら前後の情報を 再確認して繋ぎ変えを続行
  111. 111. Lock-free SkipListへの挿入1. 最下位リストへの挿入を行う2. 下のレベルから順に上位リストのリンクを繋ぎ変える3. 繋ぎ変える途中で他人に書き換えられてたら前後の情報を 再確認して繋ぎ変えを続行
  112. 112. Lock-free SkipListでの削除1. 対象物の前後の関係を洗い出す2. 上位ノードから順番に削除マークを振って いく3. 上位ノードから順に追い出していく
  113. 113. そんなので平気なの?詳しく追ってみましょう
  114. 114. 挿入・挿入の衝突 やりなおすだけ 割り込んだ最下位レベルで割り込まれた場合は初めからやり直す通常のLock-free Listと同様の作法
  115. 115. 挿入・挿入の衝突 割り込んだ最下位以外で割りこまれたらそこから再開最下位以外は厳密に一貫している必要はないのでOK
  116. 116. 挿入・削除の衝突 削除開始削除はLock-free Listと同じくマーキングによってCASが失敗する仕組みになっているそしてやり直すだけ
  117. 117. ポイント削除・挿入ともに「一番下のリストへの操作の成功・失敗」を全体の成功・失敗と見なす挿入は下のリストから。削除は上のリストから。リストのイテレートが上のレベルからなので衝突した際に複雑な手続きにならないようにした配慮Java.util.concurrentのファミリーに居るクレイジーな人はコードを読んでみよう☆DougLea先生…
  118. 118. Lock-free Btree恐るるに足らず!
  119. 119. みんな大好きBtree 18 16 28 5 7 13 16 22 25 33HashMapと並ぶデータベースの基礎技術O(log n)のアクセス速度で大量のデータに向く
  120. 120. 普通のBtreeの挿入操作 26 18 16 25 28 28 足りない! 5 7 13 16 22 22 25 26 33 Splitノードに余裕がなくなった時にSplit操作を行う余裕のないノードしかSplit操作は行われない削除はノードの中身が半分を割った時にmerge操作
  121. 121. Lock-free!
  122. 122. Lock-free化 Root CAS 複製 複製 複製 ここに挿入したい必要なデータをすべて複製してしまえばいい
  123. 123. Lock-free化 Root Split必要なデータをすべて複製してしまえばいいSplitやMergeも全く同様。部分木を丸ごと作り直す
  124. 124. Lock-free化 Root 割り込まれたので割り込んだ別スレッ CAS失敗→初めから ド やりなおし必要なデータをすべて複製してしまえばいいSplitやMergeも全く同様。部分木を丸ごと作り直す衝突したらそのスレッドは初めからやり直しすべての操作はRootに対するCASで直列化
  125. 125. 超☆遅いまともなアルゴリズムが他にあるはずなのでまた調べてきます…。
  126. 126. 話題BtreeがLockFreeになって喜ぶ人は案外尐ないBtreeの部分ロックをDBの論理的競合解決に使ってるロックがなくなったら上のレイヤーで競合解決しないといけない たいてい性能が出ないとかなんとか…
  127. 127. Dynamic Software Transactional Memory 何千箇所でもCASだけで同時に!
  128. 128. Lock-freeはすごいけど・・・CASだけで操作するのは疲れたよ・・・複数個所を一気に更新できたら簡単なのに・・・
  129. 129. Lock-freeには限界があるLock-freeなデータ構造は単体で扱う分には頑健で強固でスケーラブル! でも、あなたが欲しかったのは 本当にそれでしょうか?
  130. 130. あなたが本当に欲しかった物トイレのカギを考えましょう この姿じゃ男子トイレ から出れない…!
  131. 131. あなたが本当に欲しかった物今をときめく男の娘に必要なのは 「堅牢なだけのトイレの鍵ではない…!」いくらデータ構造が強固でスケーラブルになっても実地で使えなければ意味がない…!「Composabilityがない」とも言うコードを使いまわせないのはソフトウェア工学の 世の中のすべてのロックを敗北 生まれる前に消し去りたい 過去と未来のすべてのロックをこの手で
  132. 132. そこでSoftwareTransactionalMemory複数の操作をSerializableな分離レベルで実行1ワードのCASのみを使う単一ロックよりもスケールする タダ飯の時代の再来!?
  133. 133. SoftwareTransactionalMemory外部からこのように見える場合を想定 A B
  134. 134. SoftwareTransactionalMemory内部ではこのように扱う A Old New Owner Commit B Old New Owner Abort
  135. 135. SoftwareTransactionalMemoryどういう意味か A Old New Owner Commit スレッド スレッドの のステー ステータス タス B Old New Owner Abort
  136. 136. SoftwareTransactionalMemoryステータスは Commit (既に作業を終えた) Abort (一時的に作業を止めた) Active (現在作業中である) Commit のどれかの状態をとる スレッド スレッドの のステーCommit状態ならNew ステータス タス Abort状態ならOld )を読みだす! Abort Active状態なら競合解決(後述)へ
  137. 137. SoftwareTransactionalMemoryつまり これらが最新の値 A Old New Owner Commit B Old New Owner Abort
  138. 138. SoftwareTransactionalMemoryA,Bの2つを一気に更新する事例 A Commit B Abort
  139. 139. SoftwareTransactionalMemory自分のステータスを保存 Active A Commit B Abort
  140. 140. SoftwareTransactionalMemory ActiveCASA CommitCASB Abort
  141. 141. SoftwareTransactionalMemory CAS Commit ActiveA 最新の値B
  142. 142. 邪魔が入る場合 ActiveA B欲しい… ActiveB
  143. 143. 邪魔が入る場合 Active AbortA CAS 最新の値 ActiveB Bを獲得!
  144. 144. どう凄いのかA 複数個所の最新情報B がCAS1回で変わるC Commit Active AbortDE
  145. 145. /人◕ ‿‿ ◕人\「その壮大過ぎる祈りを叶えた対価に、STMが背負うことになる呪いの量が分かるかい?」
  146. 146. The Art ofMultiprocessorProgramming読みましょう!略称TAoMP本
  147. 147. Transaction on Key Value Store 僕の修士論文(予定)
  148. 148. 研究背景Webサービスを支えるためにスケールアウト可能なデータストアであるキーバリューストアが広く利用されているmemcached, flare, cassandraしかしキーバリューストアは一度に一つのキーバリューペアにしかアクセスできない複数のクライアントから並行してアクセスする際に一貫性を保てない
  149. 149. キーバリューストアでのCASすべてのキーバリューペアがバリュー値と共にuniq値を持っていて、書き換え時に更新される1. Getsコマンドでuniq値を獲得2. CASコマンドでuniq値を指定しながら保存3. uniq値が一致した場合に限り保存が成功 成功なら1~2間で値が更新されていないことアトミックな操作ができる が保証される
  150. 150. キーバリューストア上でのロック並行制御を実現するための一番素直な実装ロックを保持するキーバリューペアを用意キーバリューストアのCASコマンドで可能 キー バリュー K V
  151. 151. ロックを実装すると上手く動きそうに見えるキー バ lock(a); a リュー lock(b); set(a, 1); set(b, 2); b unlock(a); unlock(b);
  152. 152. ロックを実装するとクライアントが離脱すると破綻するタイムアウトでアンロックしてもデータが一貫しない 永久にロッ lock(a);キー バ ク a リュー されたまま lock(b); set(a, 1); 離脱 set(b, 2); b unlock(a); unlock(b);
  153. 153. 提案キーバリューストア上にトランザクションを実装キーバリューストアには手を加えず、クライアントライブラリとして実現 基本的性能や障害耐性はキーバリューストアの実装に依存 サーバが落ちても大丈夫な分散KVSなら信頼性も安心クライアントが離脱しても大丈夫トランザクションを用いて一貫性を保つ トランザクション キーバリュースト ア
  154. 154. 基本方針Dynamic STMを応用メモリの間接参照の代わりに、キーバリューストアに間接化を導入トランザクショナルキーバリューペアというデータ構造を構築
  155. 155. キーバリューストアでの間接化キーは文字列なのでバリューの中に挿入できる キー バリュー A B B C A B C
  156. 156. 複数のキーを格納複数のキーを格納する事もできる キー バ リュー A ab|cd|ef ab value1 cd value2 ef value3
  157. 157. 複数のキーを保持以後はこのように図示 A ab cd ef value1 value2 value3
  158. 158. 提案手法ソフトウェアトランザクショナルメモリの一つであるDynamic STMを応用メモリの間接参照の代わりに、キーバリューストアに間接化を導入トランザクショナルキーバリューペアというデータ構造を構築
  159. 159. トランザクションナルキーバリューペア (TKVP) 通常のキーバリューペア(KVP)をトランザ クションのために拡張したもの key value トランザクション 古い値 新しい値 ステータス oMel1d Sde93A 8Yu4naplE2 key old new status u40F... iLdsa. .. saFASFD... activ value value’ e
  160. 160. トランザクショナルキーバリューペ アstatusの値によって読み出し方を分岐 commited:Newの値を読み出す abort:Oldの値を読み出す active:競合を解決する(後述) key old new status activ value value’ commited abort e
  161. 161. トランザクショナルキーバリューペア (TKVP)キーバリューストアにはこのように保存される バ キー リュー Sde93Au40F... key oMel1diLdsa... 8Yu4naplE2saFAS FD… Sde93A u40F... value old oMel1d iLdsa. .. value’new 8Yu4naplE2 activ status saFASFD... e
  162. 162. トランザクショナルキーバリューペ ア key old new status value value’ commited commited 省略して表記
  163. 163. トランザクションの実例トランザクションを用いて Transaction{ set(a, 1); // a=1 set(b, 2); // b=2 set(c, 3); // c=3 }を実現する
  164. 164. トランザクションの概観ステータスを初期化 初期化必要なTKVPを更新 更新ステータスをコミッ コミットトへ no 成功? yes 終了
  165. 165. 初期化自身のステータスをActiveとしてキーバリューストアに新たに保存するこの際のキーはランダムな文字列を生成する Transaction{ set(a, 1); // a=1 set(b, 2); // b=2 set(c, 3); // c=3 } set(hoge,active) activ hoge
  166. 166. トランザクションの概観ステータスを初期化 初期化必要なTKVPを更新 更新ステータスをコミッ コミットトへ no 成功? yes 終了
  167. 167. TKVPの最新の値(おさらい) Statusの値によって最新の値が変わる a commited 4 8 commitedならa→8 a abort 4 8最新の値 abortならa→4
  168. 168. 更新操作 TKVPの所有権を奪って Transaction{ oldが「これまでの最新の値」 set(a, 1); // a=1 newが「これからの最新の値」 set(b, 2); // b=2 をそれぞれ指すよう書き換える set(c, 3); // c=3 } commitedなら CAS commited a old new aaaa hoge set(fEe09d, 1); cas(a, 4 8 1 [old,fEe09d,hoge]) active 初期化時に保存 abortなら CAS abort したステータス a old new aaaa hoge set(fEe09d, 1); cas(a, 4 8 1 [old,fEe09d,hoge]) active
  169. 169. 複数のTKVP更新同一のトランザクションステータスを指すよう複数のTKVPを書き換える Transaction{ set(a, 1); // a=1 set(b, 2); // b=2 1 set(c, 3); // c=3 a } hoge CAS 2 b active hoge CAS 3 c hoge
  170. 170. トランザクションの概観 ステータスを初期 初期化 化 必要なTKVPを更新 更新 コミット ステータスをコ ミットへ no 成功? yes 終了
  171. 171. コミットステータスがactiveであることを確認してcommitedへ書き換える Transaction{ set(a, 1); // a=1 set(b, 2); // b=2 set(c, 3); // c=3 } cas(hoge, commited); Activ hoge commited CAS e
  172. 172. コミット 書き換えるステータスは必ずキーバリュー ペア一つ 一斉にnewの指している物が最新になる 1 a hoge CAS個数制限 2 Activ commited なし b e hoge 3 c 最新の値 hoge
  173. 173. トランザクションの概観ステータスを初 初期化期化 更新必要なKVPをオープンしながら更新 コミット noステータスをコ 成功?ミット状態へ yes 終了
  174. 174. アボートコミット時にステータスがabortに書き換えられていたらコミット失敗。トランザクションを始めからやり直すabortに書き変わっているというのはどういう状況か hoge abort commited
  175. 175. トランザクショナルキーバリューペ アステータスの値によって読み出し方を分岐 commited:Newの値を読み出す abort:Oldの値を読み出す active:競合を解決する key old new status activ value value’ e
  176. 176. 競合する場合を考える トランザクション hogeTransaction{ set(a, 1); set(b, 2); トランザクション fuga Transaction{ set(b, 999);
  177. 177. TKVPの所有者がactiveだった場合所有者のstatusをabortへ書き換える トランザクションTKVPを奪う fuga Transaction{ a hoge set(b, 999); 8 1 aはロール CAS cas(hoge,abort) バック b fuga hoge active リトライ abort CAS b欲し bを獲得 い 7 2 999最新の値 active 続行
  178. 178. クライアントが離脱した場合トランザクション途中で故障などによって離脱したらいずれ誰かからabortされる。 CAS 離脱 active abort
  179. 179. 性能測定中…
  180. 180. 問題点ゴミが増え続けるもともとDynamicSTMはGC前提 ゴミ active commited a active commited active commited b active c
  181. 181. 解決策双方向化により参照カウンタGCここ実装途中github.com/kumagi/kvtx2 active commited a active commited active commited b active c
  182. 182. Future Work強い一貫性を実現できるのでKVS上にインデックス用にBtreeを構成しても大丈夫トランザクションと範囲検索ができればSQLだって捌けるかもCassandraなどのEventual Consistentな環境でもクライアント側の論理クロックを付与すればいけそうまだ絵に描いた餅ですが…
  183. 183. まとめLock-free Stack, Queue, List, Hashmap,SkipList, BtreeとDynamicSTMのアルゴリズムを解説 腑に落ちない所は懇親会で 実装の具体例は僕のGithubや懇親会で ブログ記事で読みたいネタがあったら懇親会で分散環境に応用しようと取り組んでます

×