狠狠撸

狠狠撸Share a Scribd company logo
キャッシュコヒーレントに囚われ 
ない並列カウンタ達 
#DSIRLNP 
@kumagi
この発表について 
Data Structure!
キャッシュ? 
? CPUが高速化のためにメモリの一部を切り出 
して复製している物
キャッシュ 
CPUコア 
L1キャッシュ 
L2キャッシュ 
L3キャッシュ 
メモリ
All programmer should know 
? L1 キャッシュ参照......................... 0.5 ns 
? 分岐予測ミス............................ 5 ns 
? L2 キャッシュ参照........................... 7 ns 
? Mutex lock/unlock ........................... 25 ns 
? Main memory 参照...................... 100 ns
キャッシュ 
メモリ
キャッシュコヒーレント? 
? 複数のCPUコアから見えるメモリは同一でな 
いと困る 
? つまり複数のCPUコアのキャッシュは常に最 
新の情報を保持してないと困る 
? だが常に最新の情報を全部のキャッシュに全 
て書き続けるのは速度が出ないので、まるで 
本当に全部のキャッシュにすべて書いてるか 
のように見せかけながらパフォーマンスを出 
す必要がある
キャッシュ 
メモリ 
2つのコアで 
尝2キャッシュを共有
キャッシュコヒーレント 
? 複数のL1キャッシュ間で一貫したデータを扱うため 
のキャッシュ間のプロトコル 
? Intel系CPUはMESIFプロトコル 
? AMD系CPUはMOESIプロトコル 
Core1 Core2 
L1キャッシュコヒーレント 
L1キャッシュ 
L2キャッシュ
キャッシュコヒーレントプロトコル 
? キャッシュラインが取る状態名の頭文字が由来 
– Modified: メモリよりもキャッシュの方が新しい(書き換えた) 
– Exclusive: メモリとキャッシュが同一であり、他のコアはこの 
キャッシュラインを持っていない 
– Shared: メモリとキャッシュが同一であり、他のコアもこの 
キャッシュラインを複製している 
– Invalid: 正しいキャッシュを持っていないので読むな 
– Owned: 俺がメモリだ(Shared可能なModified) 
– Forward: Sharedのボス。アクセスする際にはこいつに伺え
L1キャッシュの状態 
? 1ライン64byteで、アドレスの下位バイトが同 
じ物ごとに8wayずつ32KBまで持っている 
? 1ラインごとにMESIFのどれかのステートを 
持っている 
invalid 
shared 
exclusive 
modified 
64Byte 
全部のラインが独立して 
それぞれステートが遷移する 
32KB 
forward
キャッシュコヒーレントプロトコル 
? MESI(F)プロトコルはModifiedなデータを他のコアが読み出す際 
にメモリに書き戻す 
? Sharedなデータを書き換える時にはInvalidation要求をブロード 
キャストするためトラフィックが混む 
Modified 
Exclusive 
共有を要求 
書き換えた 
(1個下のメモリへアクセス) 
新規に読み出した 
Shared Invalid 
Invalidation要求が来た 
他のコアから貰った
キャッシュを汚す事はギルティ 
? 他のスレッドから繰り返し読む値を書き換え続けると、キャッ 
シュラインのステートはModifiedとSharedの間を行ったりき 
たりすることになる。これは激しいトラフィックを起こす。 
? 更にはModifiedからSharedになる度に下層のメモリに書き 
込まなくてはならない 
うぉー! うぉー! 
Core1 Core2 
L1キャッシュL1キャッシュ 
L2キャッシュ 
write 
read 
ぎゃー!! 
write 
Invalidate
キャッシュを汚す事はギルティ 
共有/localデータから 
Readした場合の速度 
localデータにWrite 
した場合の速度 
共有データにWrite 
した場合の速度 
http://www.1024cores.net/home/lock-free-algorithms/first-things-first から
まったくスケール 
しない!!!! 
http://www.1024cores.net/home/lock-free-algorithms/first-things-first から
NUMAでのキャッシュコヒーレント 
? 最近のサーバはマルチソケットCPUがよく使 
われる
cc-NUMA 
? 複数のCPUソケットから同一のメモリ空間が 
見えて欲しい(まるでマルチコアCPUのように) 
? キャッシュ衝突するとQPIアクセスの刑 
– かつては拷問にも使われていたQPI経由のキャッ 
シュコヒーレント
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~
キャッシュ 
隣のCPUのキャッシュは 
自分のメインメモリよりも遠い!!! 
メモリメモリ
cc-NUMAの時代 
? キャッシュ衝突をとにかく減らすアルゴリズム 
が望まれている
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_; 
};
Combining Tree 
? 1つのキャッシュラインを取り合うのが2スレッ 
ドまでになるようトーナメントを構成する 
? トーナメントでぶつかったスレッドは、先に来 
たスレッドに後に来たスレッドが値を託して待 
つ 
? キャッシュコヒーレントトラフィックを劇的に削 
減!
Combining Tree 
? トーナメントのてっぺんを読めば値が読める 
root
Combining Treeアルゴリズム 
+1 +1 +2 +3 +2 +1
Combining Treeアルゴリズム 
+1 +1 +2 +3 +2 +1
Combining Treeアルゴリズム 
+1 
待 
+3 
+3 
+3 
待
Combining Treeアルゴリズム 
+4 
待 
待 
+6 
待 
待
Combining Treeアルゴリズム 
+10 
待 
待 
待 
待 
待
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
詳細なアルゴリズム 
? 各ノードはIdle, First, Second, Rootのどれかの 
状態を持つ 
– Idle: どのスレッドも触ってない 
– First: 最初のスレッドが既に触った 
– Second: 二つ目のスレッドが触った 
– Root: てっぺん(遷移しない) 
? 更にノードはロックを2つ持っている
Combining Tree 
? 初めに木を登りながらステートの変更を行う 
– Idleな物をFirstにしていく 
– ロックを取ってステートを変えて即アンロック 
Root 
First 
First
Combining Tree 
? 初めに木を登りながらステートの決定を行う 
– Firstを見たらSecondにして第二ロックを獲得 
? こっちのロックはすぐには手放さない 
Root 
First 
Second 
First First 
Second
Combining Tree 
? 初めに木を登りながらステートの決定を行う 
– Secondにしたらそこで木を登るの停止 
Root 
First 
Second 
First 
Second
Combining Tree 
? 木を登るのをやめた所で、自分の過去の経 
路の第二ロックを獲得し直しながら値を清算 
Root 
First 
Second 
First 
Second
Combining Tree 
? 木を登るのをやめた所で、自分の過去の経 
路の第二ロックを獲得し直しながら値を清算 
Root 
First 
Second 
+1 +2 
First 
Second
Combining Tree 
? 清算しきった値を最初に自分が登った一番高 
い所に書き込んでアンロックするのが基本戦 
略 
Root 
First 
Second 
First 
+3 
Second
Combining Tree 
? 清算しきった値を最初に自分が登った一番高 
い所に書き込んでアンロック 
Root 
+3 
SFeicrosntd 
Second 
Second 
First
Combining Tree 
? Secondのステートを見たら他のスレッドが清 
算中なのでそれを待つ(第二ロック獲得待ち) 
Root 
Lock! 
SFeicrosntd 
Second 
Second 
Lock! 
First
Combining Tree 
? 清算し終わった値を持って再帰的に自分の 
値として生産する 
Root 
SFeicrosntd 
Second 
Second 
First 
+4
Combining Tree 
? 平均して木の深さnに対して2*n回のロックと 
アンロックを使うことになる 
– 大丈夫なの???? 
? Mutex lock/unlock ........................... 25 ns 
? QPI経由で隣のメモリ参照.............. 200 ns~ 
キャッシュ衝突のペナルティを 
考えると余裕でペイする
Combining Tree 
? 衝突がない場合でも2*n回のロック?アンロッ 
ク 
? レイテンシという点において非常に悪い 
– 改良として1ノードに3スレッド以上ぶら下げて木 
の深さを減らすパターンもあるが、複雑さがマッ 
ハでレイテンシはむしろ悪化
Combining Funnel 
? Funnel = 上戸 
? 乱数ベースで負荷を低減 
– アルゴリズムはEliminationに近い 
– Eliminationと組み合わせることも可能 
Counter
Combining Funnel 
? 配列の中のランダムな箇所にCAS命令で自分 
のタスクを置く 
? ランダム時間の待機後、CASでタスクを引き上 
げて次のレイヤーに進む(ここが上戸っぽい) 
+2 +1
Combining Funnel 
? 置こうと思った場所に先客が居たらそいつと 
操作を結合して次のレイヤーに進む 
+3したい 
+2 +1
Combining Funnel 
? 置こうと思った場所に先客が居たらそいつと 
操作を結合して次のレイヤーに進む 
+5せざるを得ない! 
待+1
Combining Funnel 
? 置こうと思った場所に先客が居たらそいつと 
操作を結合して次のレイヤーに進む 
あっ待てって言われてる 
待+1 
+5 
Counter
Combining Funnel 
? カウンタのレイヤーまで到達したらそいつに 
Compare And Swap 
? 感動的に簡単しかも速い
Combining Funnel 
Combining Funnels A Dynamic Approach To Software Combining より
SNZI 
? 数を数えようとするから残念なことになるん 
や!諦めろ! 
– ゼロかどうか分かればええ! 
? Scalable-Non-Zero-Indicatorの略でSNZI 
– pronounced "snazzy" : (和訳)粋な、おしゃれな、 
優雅な、エレガントな、格好いい
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_; 
};
SNZIの実装 
? キャッシュラインで木構造を作る 
0 
0 0 
ここが0かどうかを 
見れば良い 
0 0 0 0
SNZIの実装 
? 木の各ノードが1CPU 
– 上から数えてi番目のノードはi番目のCPUに割当 
0 
1 
0 1 
0 
01 0 0 0 
root以外を0から1に増やすときには1個上のノード 
の値をインクリメントする 
rootは常に普通にインクリメントするだけ
SNZIの実装 
? 木の各ノードが1CPU 
– 上から数えてi番目のノードはi番目のCPUに割当 
0 
1 
0 1 
0 
01 0 0 0 
root以外を1から0に減らすときには1個上のノード 
の値をデクリメントする 
rootは常に普通にデクリメントするだけ
SNZIの実装 
? 木の各ノードが1CPU 
– 上から数えてi番目のノードはi番目のCPUに割当 
0 
1 
0 2 
1 
0 
01 0 0 0 
それ以外のときは普通にインクリメントするだけ 
=上のノードのキャッシュラインに触れずに済む
SNZI 
? すごくスケールする!!! 
SNZI: Scalable NonZero Indicatorsより
しかもロックフリー!!
SNZIの実装 
? 厳密には複数のスレッドが同一のノードにア 
クセスしに来た際に0?1周りで細かい調停が 
必要 
0から1のときは、先に自分の箇所の値を1/2という値にしてから上のノードをインクリメン 
トしにいき、そのあとで1/2を1にCASを試みる。もし上のノードのインクリメントに成功した 
後に1/2→1のCASに失敗したら上のノードをデクリメントしておく 
? デクリメントの数はその前に行われたインクリ 
メントの数を越えては行けない(Read-Write 
ロックなどに用いるには充分)
SkySTMへの適用 
What kinds of applications can benefit from Transactional Memory? より
時間が無くて書けなかったこと 
? STMの高速化のためにSNZIが必要とかいっと 
きながら実際にSNZIをRead-Write-Lockに用い 
たSkySTMは割とあっさりTLRWに負けている 
– TLRWはByteLockっていうまた別のロックプロトコ 
ルを用いてる(こっちの話はまた今度) 
? SNZIのためにHTMを用いたやつがあって凄い 
速い 
– HTMの使い方についてもまた今度
HTM+SNZI 
HTM 
の力 
What kinds of applications can benefit from Transactional Memory? より

More Related Content

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