キャッシュコヒーレンシに囚われない並列カウンタ達

6K Views

April 11, 23

スライド概要

profile-image

分散システムとかデータベースとかロックフリーとかが好きです。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

キャッシュコヒーレントに囚われ ない並列カウンタ達 #DSIRLNP @kumagi

2.

この発表について Data Structure!

3.

キャッシュ? • CPUが高速化のためにメモリの一部を切り出 して複製している物

4.

キャッシュ CPUコア L1キャッシュ L2キャッシュ L3キャッシュ メモリ

5.

All programmer should know • • • • • L1 キャッシュ 参照 ......................... 0.5 ns 分岐予測ミス............................ 5 ns L2 キャッシュ 参照 ........................... 7 ns Mutex lock/unlock ........................... 25 ns Main memory 参照 ...................... 100 ns

6.

キャッシュ メモリ

7.

キャッシュコヒーレント? • 複数のCPUコアから見えるメモリは同一でな いと困る • つまり複数のCPUコアのキャッシュは常に最 新の情報を保持してないと困る • だが常に最新の情報を全部のキャッシュに全 て書き続けるのは速度が出ないので、まるで 本当に全部のキャッシュにすべて書いてるか のように見せかけながらパフォーマンスを出 す必要がある

8.

キャッシュ 2つのコアで L2キャッシュを共有 メモリ

9.

キャッシュコヒーレント • 複数のL1キャッシュ間で一貫したデータを扱うため のキャッシュ間のプロトコル • Intel系CPUはMESIFプロトコル • AMD系CPUはMOESIプロトコル Core1 Core2 L1キャッシュ コヒーレント L2キャッシュ L1キャッシュ

10.

キャッシュコヒーレントプロトコル • キャッシュラインが取る状態名の頭文字が由来 – Modified: メモリよりもキャッシュの方が新しい(書き換えた) – Exclusive: メモリとキャッシュが同一であり、他のコアはこの キャッシュラインを持っていない – Shared: メモリとキャッシュが同一であり、他のコアもこの キャッシュラインを複製している – Invalid: 正しいキャッシュを持っていないので読むな – Owned: 俺がメモリだ(Shared可能なModified) – Forward: Sharedのボス。アクセスする際にはこいつに伺え

11.

L1キャッシュの状態 • 1ライン64byteで、アドレスの下位バイトが同 じ物ごとに8wayずつ32KBまで持っている • 1ラインごとにMESIFのどれかのステートを 持っている 64Byte invalid shared exclusive modified forward 全部のラインが独立して それぞれステートが遷移する 32KB

12.

キャッシュコヒーレントプロトコル • MESI(F)プロトコルはModifiedなデータを他のコアが読み出す際 にメモリに書き戻す • Sharedなデータを書き換える時にはInvalidation要求をブロード キャストするためトラフィックが混む Modified 書き換えた 共有を要求 (1個下のメモリへアクセス) Exclusive 新規に読み出した Shared Invalidation要求が来た 他のコアから貰った Invalid

13.

キャッシュを汚す事はギルティ • 他のスレッドから繰り返し読む値を書き換え続けると、キャッ シュラインのステートはModifiedとSharedの間を行ったりき たりすることになる。これは激しいトラフィックを起こす。 • 更にはModifiedからSharedになる度に下層のメモリに書き 込まなくてはならない うぉー! うぉー! Core1 write ぎゃー!! L1キャッシュ Invalidate Core2 read L1キャッシュ write L2キャッシュ

14.

キャッシュを汚す事はギルティ 共有/localデータから Readした場合の速度 localデータにWrite した場合の速度 共有データにWrite した場合の速度 http://www.1024cores.net/home/lock-free-algorithms/first-things-first から

15.

まったくスケール しない!!!! http://www.1024cores.net/home/lock-free-algorithms/first-things-first から

16.

NUMAでのキャッシュコヒーレント • 最近のサーバはマルチソケットCPUがよく使 われる

17.

cc-NUMA • 複数のCPUソケットから同一のメモリ空間が 見えて欲しい(まるでマルチコアCPUのように) • キャッシュ衝突するとQPIアクセスの刑 – かつては拷問にも使われていたQPI経由のキャッ シュコヒーレント

18.

All programmer should know • • • • • • L1 キャッシュ 参照 ......................... 0.5 ns 分岐予測ミス............................ 5 ns L2 キャッシュ 参照 ........................... 7 ns Mutex lock/unlock ........................... 25 ns Main memory 参照 ...................... 100 ns QPI経由で隣のメモリ参照.............. 200 ns~

19.

キャッシュ 隣のCPUのキャッシュは 自分のメインメモリよりも遠い!!! メモリ メモリ

20.

cc-NUMAの時代 • キャッシュ衝突をとにかく減らすアルゴリズム が望まれている

21.

Combining Tree • 以下のインタフェースを備えるカウンタ – add(int a):数値aを足す – read():現在の数値を読む • ただしスケーラブル! 擬似コード class counter { public: counter() : cnt_(0) {} void add(int a) { cnt_ += a; } int read() const { return cnt_; } private: int cnt_; };

22.

Combining Tree • 1つのキャッシュラインを取り合うのが2スレッ ドまでになるようトーナメントを構成する • トーナメントでぶつかったスレッドは、先に来 たスレッドに後に来たスレッドが値を託して待 つ • キャッシュコヒーレントトラフィックを劇的に削 減!

23.

Combining Tree • トーナメントのてっぺんを読めば値が読める root

24.

Combining Treeアルゴリズム +1 +1 +2 +3 +2 +1

25.

Combining Treeアルゴリズム +1 +1 +2 +3 +2 +1

26.

Combining Treeアルゴリズム +3 +1 +3 待 +3 待

27.

Combining Treeアルゴリズム +6 +4 待 待 待 待

28.

Combining Treeアルゴリズム +10 待 待 待 待 待

29.

Combining Treeアルゴリズム • • • • • • 結合法則を用いて計算の合成を行う x+1+1+2+3+2+1 x + 1 + (1 + 2) + 3 + (2 + 1) x + (1 + 3) + (3 + 3) x + (4 + 6) x + 10

30.

詳細なアルゴリズム • 各ノードはIdle, First, Second, Rootのどれかの 状態を持つ – Idle: どのスレッドも触ってない – First: 最初のスレッドが既に触った – Second: 二つ目のスレッドが触った – Root: てっぺん(遷移しない) • 更にノードはロックを2つ持っている

31.

Combining Tree • 初めに木を登りながらステートの変更を行う – Idleな物をFirstにしていく – ロックを取ってステートを変えて即アンロック Root First First

32.

Combining Tree • 初めに木を登りながらステートの決定を行う – Firstを見たらSecondにして第二ロックを獲得 • こっちのロックはすぐには手放さない Root Second First First Second First

33.

Combining Tree • 初めに木を登りながらステートの決定を行う – Secondにしたらそこで木を登るの停止 Root Second First First Second

34.

Combining Tree • 木を登るのをやめた所で、自分の過去の経 路の第二ロックを獲得し直しながら値を清算 Root Second First First Second

35.

Combining Tree • 木を登るのをやめた所で、自分の過去の経 路の第二ロックを獲得し直しながら値を清算 Root Second First +1 First +2 Second

36.

Combining Tree • 清算しきった値を最初に自分が登った一番高 い所に書き込んでアンロックするのが基本戦 略 Root +3 Second First First Second

37.

Combining Tree • 清算しきった値を最初に自分が登った一番高 い所に書き込んでアンロック Root +3 Second First First Second

38.

Combining Tree • Secondのステートを見たら他のスレッドが清 算中なのでそれを待つ(第二ロック獲得待ち) Root Lock! Lock! Second First First Second

39.

Combining Tree • 清算し終わった値を持って再帰的に自分の 値として生産する +4 Root Second First First Second

40.

Combining Tree • 平均して木の深さnに対して2*n回のロックと アンロックを使うことになる – 大丈夫なの・・・? • Mutex lock/unlock ........................... 25 ns • QPI経由で隣のメモリ参照.............. 200 ns~ キャッシュ衝突のペナルティを 考えると余裕でペイする

41.

Combining Tree • 衝突がない場合でも2*n回のロック・アンロッ ク • レイテンシという点において非常に悪い – 改良として1ノードに3スレッド以上ぶら下げて木 の深さを減らすパターンもあるが、複雑さがマッ ハでレイテンシはむしろ悪化

42.

Combining Funnel • Funnel = 上戸 • 乱数ベースで負荷を低減 – アルゴリズムはEliminationに近い – Eliminationと組み合わせることも可能 Counter

43.

Combining Funnel • 配列の中のランダムな箇所にCAS命令で自分 のタスクを置く • ランダム時間の待機後、CASでタスクを引き上 げて次のレイヤーに進む(ここが上戸っぽい) +2 +1

44.

Combining Funnel • 置こうと思った場所に先客が居たらそいつと 操作を結合して次のレイヤーに進む +3したい +2 +1

45.

Combining Funnel • 置こうと思った場所に先客が居たらそいつと 操作を結合して次のレイヤーに進む +5せざるを得ない! 待 +1

46.

Combining Funnel • 置こうと思った場所に先客が居たらそいつと 操作を結合して次のレイヤーに進む あっ待てって言われてる 待 +1 +5 Counter

47.

Combining Funnel • カウンタのレイヤーまで到達したらそいつに Compare And Swap • 感動的に簡単しかも速い

48.

Combining Funnel Combining Funnels A Dynamic Approach To Software Combining より

49.

SNZI • 数を数えようとするから残念なことになるん や!諦めろ! – ゼロかどうか分かればええ! • Scalable-Non-Zero-Indicatorの略でSNZY – pronounced "snazzy" : (和訳)粋な、おしゃれな、 優雅な、エレガントな、格好いい

50.

SNZIのセマンティクス • 数字が読めないカウンタ • これをスケーラブルにする 擬似コード class snzi { public: counter() : cnt_(0) {} void inc() { cnt_ += 1; } void dec() { cnt_ -= 1; } bool is_zero() const { return cnt_ == 0; } private: int cnt_; };

51.

SNZIの実装 • キャッシュラインで木構造を作る ここが0かどうかを 見れば良い 0 0 0 0 0 0 0

52.

SNZIの実装 • 木の各ノードが1CPU – 上から数えてi番目のノードはi番目のCPUに割当 0 1 0 1 0 1 0 0 0 0 root以外を0から1に増やすときには1個上のノード の値をインクリメントする rootは常に普通にインクリメントするだけ

53.

SNZIの実装 • 木の各ノードが1CPU – 上から数えてi番目のノードはi番目のCPUに割当 0 1 0 1 0 1 0 0 0 0 root以外を1から0に減らすときには1個上のノード の値をデクリメントする rootは常に普通にデクリメントするだけ

54.

SNZIの実装 • 木の各ノードが1CPU – 上から数えてi番目のノードはi番目のCPUに割当 0 1 0 1 2 0 1 0 0 0 0 それ以外のときは普通にインクリメントするだけ =上のノードのキャッシュラインに触れずに済む

55.

SNZI • すごくスケールする!!! SNZI: Scalable NonZero Indicatorsより

56.

しかもロックフリー!!

57.

SNZIの実装 • 厳密には複数のスレッドが同一のノードにア クセスしに来た際に0⇔1周りで細かい調停が 必要 0から1のときは、先に自分の箇所の値を1/2という値にしてから上のノードをインクリメン トしにいき、そのあとで1/2を1にCASを試みる。もし上のノードのインクリメントに成功した 後に1/2→1のCASに失敗したら上のノードをデクリメントしておく • デクリメントの数はその前に行われたインクリ メントの数を越えては行けない(Read-Write ロックなどに用いるには充分)

58.

SkySTMへの適用 What kinds of applications can benefit from Transactional Memory? より

59.

時間が無くて書けなかったこと • STMの高速化のためにSNZIが必要とかいっと きながら実際にSNZIをRead-Write-Lockに用い たSkySTMは割とあっさりTLRWに負けている – TLRWはByteLockっていうまた別のロックプロトコ ルを用いてる(こっちの話はまた今度) • SNZIのためにHTMを用いたやつがあって凄い 速い – HTMの使い方についてもまた今度

60.

HTM+SNZI 2倍 What kinds of applications can benefit from Transactional Memory? より