並列プログラミング入門!&おさらい!並列プログラミングカンファレンス2010-01-31
お断りとお願い!この発表では並列プログラミング≒マルチスレッドプログラミングです!並列プログラムの眠い話や嫌な話です!発表者がWindowsメインなプログラマなのでそれに根ざす情報の偏り等がありますが、そこは勘弁してね!間違った情報・記述に気付いたら指摘してね!連絡先twitter: wraith13 (推奨)Mail: wraith@trickpalace.net
発表概要!並列プログラミングのススメ!並列プログラミングの仕組み!並列プログラミングの問題!並列プログラミングのミソ!並列プログラミングのいろいろ!
並列プログラミングのススメ!並列プログラミング 入門!&おさらい!
並列プログラミングのススメ!待ち時間の多い複数のタスクは並列で処理するとうんと効率的になるよ!昨今のCPUは並列向けだよ!GUIとそれ以外は別スレッドにしようね!数秒以上かかる可能性のある処理はその進捗状況も出すようにしようね!キャンセルもできるようにしようね!キャンセルできなとユーザーは暴挙に出るよ!グリッドコンピューティングも広義での並列だよ!冗長構成の意味もあるよ!
並列プログラムの仕組み!並列プログラミング 入門!&おさらい!
並列プログラミングの仕組み!シングルなコア・プロセッサの場合一度に複数の処理を並列に実行するなんてことは物理的に無理!各処理を時分割(タイムスライス)し順番に実行する。時間CPUスレッドAスレッドBスレッドC
並列プログラミングの仕組み!シングルなコア・プロセッサの場合タイマ割り込み+コンテキストスイッチ割り込み:なんらかのイベントが発生した時に実行中の処理をほったらかして、割り込みハンドラとして設定されている処理を実行するCPUの機能。コンテキストスイッチ:CPUの各種レジスタの状態(どの処理をどのように実行中であったか->コンテキスト)の切り替え(スイッチ)。タイマ割り込み以外でもI/O待ちなどのタイミングでコンテキストスイッチが行われる。
並列プログラミングの仕組み!マルチなコア・プロセッサの場合シングルなコア・プロセッサの場合と違い、一度に複数の処理を並列に実行可能!
並列プログラミングの仕組み!マルチなコア・プロセッサの場合シングルなコア・プロセッサの場合と違い、一度に複数の処理を並列に実行可能!でも、実際には大量のプロセス・スレッドをまわさないといけないので、シングルなコア・プロセッサでやっていたことをそれぞれのコア・プロセッサで分担するだけで、本質は変わらない。
並列プログラミングの問題!並列プログラミング 入門!&おさらい!
並列プログラミングの問題!並列プログラミングには問題が特盛り!残念ながら並列プログラミンには非常に多くの問題があります!嵌って泣かないようにどのような問題があるのか一通りおさえて起きましょう!
並列プログラミングの問題!正しくコーディングすることが難しいロックやメモリバリアの類の漏れがあったり、デッドロックを起こすようなコードになっていてもそれをソースコード上で追跡・確認することが困難。ブラックリスト的手法で、問題となる共有リソース(グローバル変数やAPI呼び出しを含む)へのアクセスをカプセル化することである程度対処可能。あるべき姿としては、共有リソースへのアクセスはホワイトリスト方式が望ましいと思われる。ブラックリスト方式では漏れが出やすく、漏れたものは再現性が悪くデバッグも困難な悪質なバグとなる。
並列プログラミングの問題!テスタビリティの劣悪さプログラム動作状態のパターンがスレッドの数だけ次元単位で増加し、全ての内部状態を網羅したテストケースなど現実的に不可能。パターンが膨大になる場合は、間引いて要所を押さえてテストするものですが、この場合、現実的に可能な範囲でテストをやっても間引き率が極端に高くなり実質テストをやってないに等しくなる。そもそも各スレッドの各種状態をテスト上意図した形で実行させるのも困難。ブルートフォース的な手法に頼らざるを得ない。
並列プログラミングの問題!1->2が0になったり3になったり?並列プログラミングに関係なく昔からある問題なのですが、ある値が1から2に変化する際にそのときどきで0に見えたり1に見えたり2に見えたり3に見えたりするという非常に嫌な問題が存在します。1と2は変化前と変化後の値なので問題ないとしても0に見えたり3に見えたりするのは問題です!2->1でも全く同じ問題が起きます。
並列プログラミングの問題!1->2が0になったり3になったり?CPU/マザーボードの動作クロックとは非同期に変化する入力信号などでこの問題は再現します。逆もしかりでCPU/マザーボード側からの出力信号も非同期で動作するデバイスから見ると同じようなことが起こりえます。そもそも動作クロックとはこのような問題を起こさない為のものでもあります。
並列プログラミングの問題!1->2が0になったり3になったり?2桁の2進数で1と2を表記すると01と10になりますが、非同期なビットの変化により値が1から2に変化する際に00,01,10,11の全てのビットパターンが現れる可能性があります。127(0111111)から128(10000000)へ変化する場合は0(00000000)~255(1111111)の範囲の値に見えてしまう可能性があります。
並列プログラミングの問題!1->2が0になったり3になったり?ハミング距離が1の場合には問題が再現しません。例えば2⇔3の場合は10⇔11で、変化するビットの数が1なので問題が起きようがない。cf. グレイコード
並列プログラミングの問題!1->2が0になったり3になったり?昨今の一般的な並列プログラミングの範囲内ではCPUネイティブな整数値がレジスタやメモリ上でそのような問題を起こすようなことはないかもしれませんが、並列プログラミングの非同期性が複数の値でひとつの意味を成すデータ上で同種の問題を招きます。「ビットの集合」が「複数の値」に姿を変えただけ。
並列プログラミングの問題!シングルトンオブジェクトの初期化問題C/C++などの低レベル寄りの言語ではシングルスレッドでは問題の無いシングルトンオブジェクトの初期化がマルチスレッドの状況下においては複数のスレッドが同時に初期化を試行し競合を起こす問題があります。関数ローカルなstatic変数などもシングルトンオブジェクトである為、同じ問題を起こす。
並列プログラミングの問題!シングルトンオブジェクトの初期化問題簡単な回避方法としてはメインスレッドで最初に初期化してしまう。後述のアウトオブオーダーの問題やCPUのコアローカルなキャッシュの問題など、嫌らしい問題があるので上記以外の方法の実装が必要な場合は信頼のおけるライブラリの利用を推奨!
並列プログラミングの問題!アウトオブオーダー実行アウトオブオーダー実行は、よく「プログラムは思った通りではなく書いた通りに動く」と言われますが、それを覆し「プログラムは書いた通りにすら動かない」ようにする為のCPUの機能!
並列プログラミングの問題!アウトオブオーダー実行アウトオブオーダー実行は、よく「プログラムは思った通りではなく書いた通りに動く」と言われますが、それを覆し「プログラムは書いた通りにすら動かない」ようにする為のCPUの機能!などと言うのは半分冗談です。<ぼそっ>でも残念ながら半分事実です。</ぼそっ>
並列プログラミングの問題!アウトオブオーダー実行アウトオブオーダー実行は、プログラムを順序どおりに実行しなくても問題ない場合にプログラムを本来の実行順序とは異なる順序で実行することでCPUがより高速に動作する為の機能です。・・・でも、「プログラムを順序どおりに実行しなくても問題ない場合」であることの判断がシングルスレッドを前提としており、マルチスレッド的な観点からはある意味、指示を無視して勝手な順序でプログラムを実行するという困った事に。
並列プログラミングの問題!アウトオブオーダー実行マルチスレッド的な観点から実行順序が重要である場合には、メモリバリアを使うことでこの問題を回避できます。同期オブジェクト関連を始めとする、マルチスレッド関連のAPI等にはメモリバリアの機能を含むものが多数あります。POSIXでは一部の関数群でメモリバリアの機能を含むものと含まないものが用意されているので注意が必要です。
並列プログラミングの問題!CPUコアローカルなキャッシュマルチコア・マルチプロセッサな環境ではそれぞれのコア・プロセッサごとにキャッシュを持っています。これは同じデータのコピーを複数持つことを意味し、あるタイミングで同じハズのデータを参照してもコアによって異なる値になります。この問題もメモリバリアにより回避することができます。
並列プログラミングの問題!スレッドローカルストレージスレッドローカルストレージ:スレッド別データの保存先。スレッド別のグローバル変数として使える。マルチスレッドなプログラミングで重宝する機能なのですが、スレッドを跨いで使うスマートポインタの類と組み合わさると、スマートポインタの参照先を解放するコードにスレッドローカルストレージへのアクセスが含まれると意図した本来アクセスするべきデータとは異なるデータにアクセスしてしまうという自体が発生し得ます。外部のAPIなどにこのような地雷があると怖い。
並列プログラミングの問題!各スレッドは均等な速度では動作しないそもそもマルチプロセス、マルチスレッド等をコントロールするOSの判断により、均等なタイムスライスとはならない。同じ系列のOSであってもタイムスライスのロジックはOSのバージョンによっても異なります。実行優先度等が同一で全く同じような処理を実行していても参照するメモリがCPUのキャッシュにたまたま存在するかどうかと言ったような条件が実行速度には著しく影響を及ぼす。
並列プログラミングの問題!並行処理のオーバーヘッドコンテキストスイッチを実行するのにも当然時間的コストが存在し、コンテキストスイッチが頻繁に行われていれば効率が悪くなります。ひとつのスレッドから見れば、潤沢に使えるCPUのキャッシュも各スレッドで使い回すことになればキャッシュのヒット率も悪くなり実行速度に影響します。ロックやメモリバリアの類のオーバーヘッド適切な範囲を超える並行処理は著しいパフォーマンス低下を招くことがあります。
並列プログラミングの問題!曖昧な「スレッドセーフ」という言葉まず「スレッドセーフな言語/ライブラリだからマルチスレッドで使っても安心」は間違いの始まり。例えば先述の「1->2が0になったり3になったり」する問題を特別な指示も必要とせず勝手に解決してくれるスレッドセーフな言語/ライブラリなど存在しません。スレッドセーフを謳う言語/ライブラリ等を使うのは有意義な事ですが、なにをもってしてスレッドセーフを謳っているのか? どういう使い方をした時にどういう問題が起きないことを保証しているのか? そのあたりが明記されていない「スレッドセーフ」は要注意です。
並列プログラミングの問題!いくつかの問題はプログラミング言語やその環境がしっかりしてれば解消/軽減できる話でもあり、よりよい言語/環境の出現が望まれる。
並列プログラミングのミソ!並列プログラミング 入門!&おさらい!
並列プログラミングのミソ!並列プログラミングの多様な問題に嵌らない為にコードに持たせるべき性質についてもおさえておきましょう!
並列プログラミングのミソ!アトミックアトミック:中間状態が存在しないこと。例えば「1->2が0になったり3になったり」も中間状態が存在することに起因する問題で、中間状態が存在しなければこの問題は発生しない。複数の値を持つオブジェクトもそのアクセスを全てメソッドで隠蔽し、ロック等の排他制御を行うことで外部からはアトミックであるように振る舞える。
並列プログラミングのミソ!ステートレスステートレス:状態を持たないこと。並列プログラミングにおける各種問題は並列動作するコンテキストから状態を共有することに起因する為、そもそも状態を持たなければ(あるいは共有しなければ)問題も発生しない。
並列プログラミングのミソ!ロックフリーロックフリー:ロックを行わないこと。並列プログラミングではロックの使用を迫られる場面が多いが、ロックは速度的なコストが高く、またデッドロックを起こす危険も孕む為、安易なロックの多用は慎むべき。しかし、下手にロックフリーを狙って余計なバグを埋め込んでしまうというリスクもあり、難しいところ。並列処理を正しくコーディングすることは難しい。
並列プログラミングのいろいろ!並列プログラミング 入門!&おさらい!
並列プログラミングのいろいろ!CPUの並列対応ハイパースレッディング:擬似的にひとつのコアが複数のコアであるように振る舞う仕組み。マルチコア:ひとつのプロセッサに複数のコアを搭載。Kernelレベルを除けば通常、ソフトウェアから見ればマルチプロセッサとの違いはない。マルチプロセッサ同じ基盤に複数のCPUを搭載。通常、同一のCPUを使用する必要がある。
並列プログラミングのいろいろ!いろいろな並列動作プロセス(process):一般的にはOS上で動作するプログラムの基本単位スレッド(thread):同一プロセス上で並列動作する仕組み/機能ファイバー(fiber):コンテキストスイッチを明示的に行う軽量なスレッドコルーチン(co-routine):プログラミング言語的にサポートされているファイバー
並列プログラミングのいろいろ!同期オブジェクト並列プログラムが非同期で動作するが故に発生する問題を回避するための仕組みのひとつ。同期オブジェクトを使うことで非同期に動作している並列プログラム間で同期を取ることができます!
並列プログラミングのいろいろ!ミューテックスもっとも基本的な排他制御用同期オブジェクト。ロックに成功したスレッドのみが処理を継続。同じミューテックスに対して一度にロックできるのはひとつのスレッドのみ。ロックに失敗したスレッドはロックに成功するまで待機、あるいは処理を放棄。Windowsではロックに成功した処理が再帰的な構造になっている場合、ロック後の再入であっても同一スレッドであればロックに成功した扱いになる。POSIXの場合はデッドロックとなる。
並列プログラミングのいろいろ!ミューテックスWindowsでもPOSXIでもメモリバリアを含む。マルチスレッド関連のAPIの中でも非常に重い部類なので注意。Windowsでは同一プロセス内のマルチスレッド状況下で、ミューテックスの代りにクリティカルセクション系のAPIを使用することもできる。
並列プログラミングのいろいろ!その他の同期オブジェクトシグナル主に待機用に使われる同期オブジェクト。セマフォ共有リソースに同時にアクセスするスレッド・プロセスの「数」を調整するのに使われる同期オブジェクト。ReaderWriterLock共有リソースへのリード目的のロックであれば同時に複数のロックを許し、ライト目的のロックであれば(リード目的を含め)ひとつのロックしか許さないような同期オブジェクト。
並列プログラミングのいろいろ!インターロック系APIインクリメント、デクリメント、値の比較、値の交換などといった操作をアトミックに行う為に提供されているAPI。速度的コストは良好。
質疑応答?並列プログラミング 入門&おさらい
ご静聴ありがとうございました!

並列プログラミング 入門!&おさらい!