できる!並列・並行プログラミング

57
できる! 並列・並行プログラミング 田中英行 <[email protected]> Tech Talk@PFI 2011/10/20

description

現在のマルチスレッドプログラミングの抱える問題点と、代替案をわかりやすく解説いたします。最近登場したConcurrent Revisionsも解説します。

Transcript of できる!並列・並行プログラミング

Page 1: できる!並列・並行プログラミング

できる! 並列・並行プログラミング

田中英行

<[email protected]>

Tech Talk@PFI 2011/10/20

Page 2: できる!並列・並行プログラミング

“The Free Lunch is Over!”

21世紀初頭、人類は岐路に立たされていた 永遠に続くかと思われたプロセッサ

高速化に陰りが見え始め プロセスの微細化は物理的限界を

迎えようとしていた プロセッサメーカはマルチコアへと走り

プログラマに困難を押し付ける 上がらないクロック 下がるIPC Out-of-OrderからIn-Orderに フリーランチを取り戻せ

果報は寝て待つ時代を再び!

Page 3: できる!並列・並行プログラミング

本日の内容

• ぼーっと寝ているだけで、プログラムが速くなる時代は終わってしまった

• どげんかせんといかん!

Page 4: できる!並列・並行プログラミング

並列・並行の違い

• 並列(Parallel)

–タスクを高速化するために、プログラムを複数のプロセッサで動作させる

–目的は高速化(速くならないのなら意味は無い)

• 並行(Concurrent)

–複数のタスクを同時に実行する

–タスクを同時に実行すること自体が目的

※よく誤解されますが、インプリによる分類ではないです

Page 5: できる!並列・並行プログラミング

分散

• 物理的に独立した、複数のノードで 並列・並行を行う

• 今回は扱いません

Page 6: できる!並列・並行プログラミング

マルチスレッドプログラミング

• 1ノード内での並列・並行プログラムに 用いる

• 並列・並行はスレッドだけじゃない – OpenMP(プラグマ)

– OpenCL/CUDA(DSL)

– Implicit Parallelism(Parallel Haskell)

–(Nested)Data Parallelism(DPH)

– Automatic-Paralellisation(お餅)

–これらの話は今回はしません

Page 7: できる!並列・並行プログラミング

スレッドとは

• ここでは次のようなものと想定

–メモリ空間を共有

–固有のスタックを持つ

–プリエンプティブ

• 実装はいろいろ

– OSスレッド(pthreadなど)

–グリーンスレッド(処理系固有のスレッド)

–軽量スレッド(Erlang/Haskellなど)

Page 8: できる!並列・並行プログラミング

マルチスレッドプログラミングは 難しい

スレッド

Page 9: できる!並列・並行プログラミング

なんで難しいのか?

• 複数のスレッドはやり取りする必要がある

–そうでなければ、ただのタスクの集合

• 複数のスレッドが同一のリソースにアクセスするとき、非決定状態が発生する

Page 10: できる!並列・並行プログラミング

簡単な例

• 次のプログラムにはバグがあります。 そのバグを指摘しなさい(1分で10級)

Page 11: できる!並列・並行プログラミング

Race Condition(競合状態)

• 複数のスレッドが同一のリソースにアクセスする際、予期しない状態になることがある

• そのような状態をRace Conditionという

• マルチスレッドプログラムは一般的に非決定な計算になるので、この手のバグの再現は困難を極めることが多い

Page 12: できる!並列・並行プログラミング

Race Conditionの例

Page 13: できる!並列・並行プログラミング

並行性制御・排他制御

• ロック – あるリソースに同時にアクセスするスレッドが一つであれば、Race Conditionは発生しない

– リソースへのアクセス権を制御する仕組み

– mutexへのロック・アンロックで実現

• 排他制御 – 同一リソースに複数スレッドがアクセスしないようにすること

– 排他制御が必要なコード上の箇所を 「クリティカルセクション」という

Page 14: できる!並列・並行プログラミング

さっきの解答

• cnt_ にロックによる排他制御を追加する

Page 15: できる!並列・並行プログラミング

そう

彼は言葉巧みに私に近づいて来ました

手を変え品を変え

言語組み込みなら大丈夫

Boostなら大丈夫

・・・

でも、待っていたのは

デバッグ地獄だったんです

Page 16: できる!並列・並行プログラミング

第二章

Locks Considered Harmful

Page 17: できる!並列・並行プログラミング

ロックは害悪である

• ロックを用いたすべてのプログラムは、バグがあるか、もしくはまだバグが見つかっていないかのどちらかである “Java Concurrency in Practice”

• ロックによる並行プログラミングは、 アセンブリでプログラムするに等しい “出典不明”

• トイレロックが取れない “P社社員”

Page 18: できる!並列・並行プログラミング

ロックが難しいと見抜けないと (ロックを扱うのは)難しい

• ロックのもたらすバグ・不具合一覧

–アンロックし忘れ

–デッドロック

–ロックの不足(Race Condition)

–ロックの過剰(全然並列にならない)

–モジュラリティの欠如

Page 19: できる!並列・並行プログラミング

(1)アンロックし忘れ

• ロックしたmutexをアンロックし忘れる

• リソースに永久にアクセスできなくなる

• 例外安全性とも関係

• 解決法:

–常にRAIIを用い、手動でのロック・アンロックは“決して行なってはいけない”

Page 20: できる!並列・並行プログラミング

(2)デッドロック

• 複数のロックを複数のスレッドが同時に獲得しようとする際に発生

• スレッドが永久に停止

• 解決法:

–必ず同じ順番でロックを取るようにする

–ロックに順序を付け、これから獲得しようとするロックを予め全て列挙、その順番でロックしなければいけない

Page 21: できる!並列・並行プログラミング

(3)ロックの不足

• クリティカルセクションでロックを忘れる

• 解決法:

–ありません

–気をつけるしかない

\(^o^)/

Page 22: できる!並列・並行プログラミング

(4)ロックの過剰

• 必要のないところでロックを取る

–粒度の大きなロック

• ハッシュテーブルで全要素をロックするなど

–粒度の小さなロックを記述するのは、非常に難しい

• 解決法:

–本質的に困難

• 特定のほげほげをfine grainedに実装しましただけで国際学会レベル

Page 23: できる!並列・並行プログラミング

(5)モジュラリティの欠如

• ロックを用いたプログラミングは互いに組み合わせることができない

• 解決法:

–原理的に不可能

\(^o^)/ \(^w^)/ \(^t^)/

Page 24: できる!並列・並行プログラミング

ロックに潜む本質的欠陥

• ロックを用いたルーチンは、Composableでない

–互いに組み合わせることができない

–例で説明します

Page 25: できる!並列・並行プログラミング

例:銀行システム

• ストレートな実装

Page 26: できる!並列・並行プログラミング

問題点

• 例:振込関数を書く

–振込処理はトランザクション

– ACI(D)が必要

• これはだめ

–中間状態が見える

Page 27: できる!並列・並行プログラミング

振込:修正

• あらかじめ両方のロックをとっておく

–ロック変数を暴露、カプセル化の崩壊

• しかし、これで良いのか?

Page 28: できる!並列・並行プログラミング

デッドロック

• デッドロックの可能性

–ロックの順番を同じにしなければならない

Page 29: できる!並列・並行プログラミング

モジュラリティの低下 ロック粒度の低下

• すべての操作が、ロックする可能性のあるmutexを列挙しなければならない

• これらすべてを一度にロックしなければならないので、一部不要なものがあったとしてもロックを回避できない

–ロックの獲得の順番は必ず守らなければならないので

Page 30: できる!並列・並行プログラミング

組み合わせ

• 例題:銀行Aから銀行Cに振込を行う。ただし、銀行Aに十分な残高がなかったら、銀行Bから銀行Cに振込を試みる。

Page 31: できる!並列・並行プログラミング

解答例:まちがい

• 予めロックを全部取る(順番は今は不問)

– aからの振込に成功した場合、 bのロックを行なってはいけない

Page 32: できる!並列・並行プログラミング

解答例:まちがい(2)

• 当然これはバグ

Page 33: できる!並列・並行プログラミング

Q:どうすればいいのか?

• A:どうにもできない

–状況に応じて必要なロックが変動する場合、それらを安全にロックすることは不可能

• これはすなわち

–ロックを用いたプログラムにモジュラリティは存在しない

–ロックを用いたプログラムを組み合わせることは不可能である

Page 34: できる!並列・並行プログラミング

第三章

Lock Must Go!

Page 35: できる!並列・並行プログラミング

マルチスレッドプログラミングに 銀の弾丸なし

• すべてを解決する夢の様なものはない

–お餅うにょーん

• 実装するものに対して適切なものを選ぶ必要がある

–方法はいろいろある

–一長一短

Page 36: できる!並列・並行プログラミング

でも、 ロックだけは 絶対に死んでもイヤ

Page 37: できる!並列・並行プログラミング

ロックの代替物

• メッセージパッシング

• Software Transactional Memory(STM)

• Concurrent Revisions –後で説明

Page 38: できる!並列・並行プログラミング

メッセージパッシング

• スレッドがそれぞれメッセージキューを持ち、スレッド間の通信はメッセージのやり取りに限る

–そもそもリソースを共有しないので、Raceが起きない

• プログラミングモデルに制限がかかる

Page 39: できる!並列・並行プログラミング

STM

• メモリ操作をAtomicity、Consistency、Isolationを満たすトランザクションとして記述する

– 各トランザクションは、シーケンシャルに実行された時と同一の結果にならなければならない

• 実装方式はいろいろ

– 楽観的並行性制御にて実行、競合したらロールバックするのが一般的

– 取り消し不可能な操作をトランザクションから排除する必要がある

Page 40: できる!並列・並行プログラミング

これらのものをサポートする 処理系

• Erlang

– immutable

– メッセージパッシング(ScalaとかHaskellも)

• Closure – 共有資源が次のいずれか

• Transactional Variable

• Thread Local

• Haskell – STM(型レベルでロールバックの正しさを保証)

Page 41: できる!並列・並行プログラミング

第四章

Concurrent Revisions

Page 42: できる!並列・並行プログラミング

Concurrent Revisions

• 最近提唱された並行性制御のための手法

–ロック無し

– Waitなし

–ロールバックなし

–ロールバックしないので取り消し不可能な操作を行なっても良い

Page 43: できる!並列・並行プログラミング

参考文献

• Sebastian Burckhardt, Alexandro Baldassion, and Daan Leijen, Concurrent Programming with Revisions and Isolation Types, in Proceedings of the ACM International Conference on Object Oriented Programming Systems Languages and Applications (OOPSLA'10), ACM SIGPLAN, Reno, NV, October 2010 – http://research.microsoft.com/en-

us/projects/revisions/

Page 44: できる!並列・並行プログラミング

概要

• バージョン管理のアナロジーで 共有リソースを扱う

Page 45: できる!並列・並行プログラミング

コンポーネント

• versioned<T> –バージョン変数

• revision –リビジョンをあらわす変数

• fork –並行に実行、子は別リビジョンに

• join –リビジョンをマージする

Page 46: できる!並列・並行プログラミング

楽観的並行性制御

• versioned変数はスレッドごとに領域が割り当てられる

– Raceが発生しない

• Joinの際に決定的(Deterministic)に コンフリクトを解消

–デフォルトでは、子の変更を優先

Page 47: できる!並列・並行プログラミング

Deterministic

• Deterministic Concurrency –非決定性がない

Page 48: できる!並列・並行プログラミング

cumulative変数

• コンフリクト解消の方法をカスタマイズ

– λorig main child -> result

Page 49: できる!並列・並行プログラミング

リビジョンツリーの形に制約

• 直接の子供(もしくはそれをjoinしたリビジョン)しかjoinできない

–正しいコンフリクト解消のため

Page 50: できる!並列・並行プログラミング

Concurrent Revisions:Pros

• Race Conditionが発生しない

• 決定的であるので、再現性の低いバグに悩まされる必要がない

–本質的に決定的である必要がある並列には 特に向いている

• (Concurrent Revisionsの)仕組みが単純 実装が容易

– STMは難しい

Page 51: できる!並列・並行プログラミング

Concurrent Revisions:Cons

• 不正なjoin

– OriginalのC#への実装はランタイムエラー

– Haskellへの実装ではRank2Polymorphismを 用いて型エラーにすることが可能

• Deterministicにコンフリクト解消ができる 必要がある

–そうでないものには向かない

Page 52: できる!並列・並行プログラミング

性能

• オーバーヘッド

–非常に軽微

Page 53: できる!並列・並行プログラミング

性能

• ゲームの並列化

– 4コアマシンでのFPS

Page 54: できる!並列・並行プログラミング

性能その他

Page 55: できる!並列・並行プログラミング

You can try it NOW!

$ git clone git://github.com/tanakh/concurrent_revisions.git $ cd concurrent_revisions.git $ ./waf configure $ ./waf build $ sudo ./waf install

Fork it!

Page 56: できる!並列・並行プログラミング

デモ

Page 57: できる!並列・並行プログラミング

まとめ

• 並行性制御に銀の弾丸なし

• でも、ロックはもう使ってはいけない

• ロックの代替を状況に応じて使い分け

–メッセージパッシング

– STM

– Concurrent Revisions