スレッド 1 - Microsoft

セッション ID:T6-308
.NET Framework 4 を
使用した並列プログラミングと
デバッグ、プロファイリング
マイクロソフト株式会社
デベロッパー & プラットフォーム統括本部
エバンジェリスト
岩田雅樹
セッションの目的とゴール
Session Objectives and Takeaways
セッションの目的
並列プログラミングの考え方を理解する
.NET Framework 4 を使用した
並列プログラミング手法を理解する
デバッガー、プロファイラーの使い方を知る
セッションのゴール
並列プログラミングに必要な考え方を
実際の開発に取り入れられるようになる
3
アジェンダ
なぜ今、並列プログラミングなのか?
.NET Framework 4 の並列拡張を理解する
ランタイム、ライブラリとその機構
並列化の考え方
デバッガー、プロファイラーを使いこなす
並列デバッガーの利用方法
プロファイリングによる問題の発見
4
並列を意識しなければならない理由
複雑化するハードウェア、ユーザーの要求
複雑化するハードウェア構成
SMP からマルチコアへの転換
階層的・局所性を意識する必要性
ユーザーの要求の変化
素早い応答、格好よい動作
5
.NET Framework 4
並列拡張
ランタイムのメカニズム
データ構造
タスク並列ライブラリ
典型的な処理の例
入力を加工、選択、出力
IEnumerable<Record> records = GetRecords();
List<Result> results = new List<Result>();
foreach(Record record in records)
{
// レコードごとに単純でない処理を行う
Result res = ProcessRecord(record);
// もし条件に合致したら結果に格納する
if (Filter(res))
results.Add(res);
}
7
手動で並列化されたコード
IEnumerable<Record> records = GetRecords();
List<Result> results = new List<Result>();
int numWorkers = Environment.ProcessorCount;
int activeWorkers = numWorkers;
IEnumerator<Record> enumerator = records.GetEnumerator();
ManualResetEvent completionEvent = new ManualResetEvent(false);
for(int i = 0; i < numWorkers; i++) {
ThreadPool.QueueUserWorkItem(delegate {
while(true) {
Record currentRecord;
lock (enumerator) {
if (!enumerator.MoveNext()) break;
currentRecord = enumerator.Current;
}
Result res = ProcessRecord(currentRecord); //レコードごとに処理を行う
if (Filter(res))
// もし条件に合致したら結果に追加
lock(results) results.Add(res);
}
if (Interlocked.Decrement(ref activeWorkers) == 0) completionEvent.Set();
});
}
completionEvent.WaitOne();
8
// 全てのワーカー スレッドが終了するのを待つ
.NET Framework 4 並列拡張
並列拡張でわかりやすい実装
目標
開発者が並列化ではなく機能に集中できる
シンプルでわかりやすい並列化コードの記述
マルチコア環境におけるスケーリング
並列化のための新しい API
タスク ベースの並列化
並列ループ、Parallel LINQ
新しい同期プリミティブ、
スレッド セーフなコレクション
スレッド プールの強化
9
.NET Framework 4 並列化サポート
同時実行
プロファイ
ラー
10
タスク並列ライブラリ
TPL (Task Parallel Library)
並列化を効率的にするランタイム
並列化をシンプルに実装可能な
開発者がコントロールできるライブラリ
11
負荷分散
CPU
1
12
CPU
2
…
CPU
N
5
7
6
8
CPU
0
CPU
1
…
CPU
N
スレッド プールの改良
ワーク スチーリング スケジューラ (TPL のみ)
ローカル
ワーク
スチーリング
キュー
…
ローカル
ワーク
スチーリング
キュー
…
Task 6
Task 1
Task 2
13
Task 3
Task 4
Task 5
タスク並列ライブラリ
タスク並列の基本単位
Task
非同期実行される処理単位
待機、キャンセル、非同期例外処理が可能
Task<TResult>
非同期に計算され、結果の取得で待機する
(Future)
14
タスクの利用方法
生成と実行
Task.Factory.StartNew(Action, ...)
Task のインスタンス生成 + Task.Start()
継続 (Continuations)
Task.ContinueWith()
Task.Factory.ContinueWhenAny() / ... All()
待機
Wait(), Task.WaitAll() / WaitAny()
例外処理
AggregateException
キャンセル
CancellationToken.IsCancellationRequested
15
並列コレクション、同期プリミティブ
並列コレクション
ConcurrentQueue / Stack / Bag
ConcurrentDictionary
BlockingCollection
同期プリミティブ
ManualResetEventSlim
SemaphoreSlim
SpinLock
SpinWait
Barrier
CountdownEvent
16
パーティショニング
{Orderable}Partitioner
キャンセル
CancellationTokenSource
CancellationToken
初期化
Lazy
ThreadLocal
並列化の考え方
データ並列とタスク並列
並列化の 2 つの考え方
データ並列とタスク並列
データ並列
並列処理可能な単位にデータを分割
並列化したタスクにデータを割り振り
並列ループ、PLINQ (Parallel LINQ)
タスク並列
並列実行可能な処理に分割、実行
Task クラスで表現
18
並列ループによるデータ並列
Parallel.For / ForEach
Parallel.For(0, 100, i => DoWork(i));
Parallel.ForEach(src, r => DoWork(r));
スレッド 1
スレッド 1
i=0
i=1
i=C+1
i=C+2
i=2
r
1
r
P+1
r
2P+1
スレッド 2
i=C+3
src
スレッド P
スレッド P
i=P*C+1
i=P*C+1
i=P*C+2
r
P
r
2P
各反復は独立していなければならない
実行の順序や配置の順序に保証はない
負荷分散は実行時に行われる
19
r
3P
Parallel LINQ によるデータ並列
入力されたデータを P 個の集合に分割する
オペレーターをパーティションごとに複製、
それぞれの独立したスレッドで動作
結果を集約
例として以下の問い合わせを考える
(from x in src.AsParallel() where f(x) select y(x)).Sum();
スレッド 1
where f(x)
src
Sum()
Sum()
スレッド P
where f(x)
20
select y(x)
select y(x)
Sum()
データ並列の例
// LINQ で表現したサンプル コード
List<Result> results = records
.Select(rec => ProcessRecord(rec))
.Where(res => Filter(res))
.ToList();
// 並列化された LINQ のコード
List<Result> results = records.AsParallel()
.Select(rec => ProcessRecord(rec))
.Where(res => Filter(res))
.ToList();
21
タスク並列の考え方
有向独立グラフでタスクの接続を考える
継続 (Continuation)
A は B, C に依存
C は D, E に依存など
{A}
{B}
{C}
{D}
{E}
22
タスク並列の例
TaskFactory factory = Task.Factory;
// Process1, 2 を開始 (t1, t2)
Task t1 = factory.StartNew(Process1);
Task t2 = factory.StartNew(Process2);
// t2 に依存する Process3 を作成
Task<int> t3 = t2.ContinueWith((t) => Process3());
// t3 の結果を待機、出力
Console.Write(t3.Result);
// t1, t2 が両方完了するまで待機
Task.WaitAll(t1, t2);
23
応用: アプリケーションでの利用
WPF, Windows フォーム アプリケーション
問題
タスクは通常、ワーカー スレッドで動作
UI の操作は UI スレッドのみ可能
解決策
TaskScheduler.
FromCurrentSynchronizationContext の利用
継続パターンの適用
24
アプリケーションで並列化を利用
UI スレッドでタスク並列を利用する例
TaskScheduler ui =
TaskScheduler.FromCurrentSynchronizationContext();
…
// 処理を tasks リストに格納
Task.Factory.ContinueWhenAll(
tasks.ToArray(), result =>
{
// ui で指定した UI スレッドで実行される
var time = watch.ElapsedMilliseconds;
label1.Content += time.ToString();
},
CancellationToken.None,
TaskContinuationOptions.None, ui);
25
まとめ: 並列化の考え方
タスク並列
処理の流れを同時実行可能なタスクへ分割
分割した処理を並列化できるように構成
構成は有向独立グラフに帰着することが可能
データ並列
タスクで処理するデータを分割
タスク並列を利用
26
並列デバッガー
Visual Studio 2010 の
新しいデバッガー
並列デバッガー
目標
アプリケーションの待機中および実行中の
タスクをデバッガーから操作する
2 つの新しいデバッガー ツール ウィンドウ
並列タスク
並列スタック
マネージおよびネイティブの
ランタイムをサポート
Task Parallel Library
- .NET (マネージ)
Parallel Patterns Library - C++ (ネイティブ)
28
並列タスク ウィンドウ
未完了の全てのタスクを表示
実行中のスレッドのコール スタックを表示
どのタスクがブロックされているか、
実行可能で待機状態なのか表示
29
並列スタック ウィンドウ
複数のコール スタックを 1 つのビューで表示
リッチな UI
(ズーム、パン、鳥瞰図、フラグ、ツールチップ)
実行中のメソッドへ簡単に移動
タスクの状態
30
最適化
プロファイラーの使い方
ボトルネックの発見と解消
同時実行の視覚化
同時実行に基づく Visual Studio 2010 の
新しいプロファイラー
スレッドの活動をタイムラインで可視化
スレッドごと、またはコアごとの
実行状態、ブロック、I/O など
非常にきめ細かい情報の提供
個々のコンテキスト スイッチにズームし、
疑わしいやり取りを追跡する
またはズーム アウトして全体の様子が
よいか悪いかを見る
32
同時実行の視覚化
サンプリング プロファイラーは
カーネル レベルのイベント ソースも利用可能
ETW (Event Tracing for Windows) がベース
=> 非常に低いオーバーヘッド
特定の同時実行ランタイムに限定されない
TPL, PPL 固有のビジュアル マーカーはある
サポート
マネージおよびネイティブのアプリケーション
Windows Vista, Windows Server 2008 以降が必要
32-bit と 64-bit
33
百聞は一見にしかず
34
プロファイラーの使用方法
管理者権限で起動
シンボル サーバーの設定
「同時実行」オプションを
パフォーマンス ウィザードで指定
35
CPU 使用状況
表示
CPU 使用率の高度な表示
Y 軸は利用可能な全てのコア
他のプロセスやアイドル時間との比較
使い方
同時実行プロファイリングにおいて
最初に確認するスタート地点
全体の使用率に対して範囲やパターンを確認
いつ実行が完全に並列化されているのか?
いつ実行が直列化されてしまっているのか?
36
スレッド
表示
コンテキスト スイッチが起こるまでの実行
スレッドがブロックされた、
誰かにブロック解除された時点のコール スタック
サンプルされたコール スタック
同じタイムライン上のディスク I/O の詳細
使い方
低い使用率、長期間の遅延の根本的な原因を探す
スレッドが実行されていない期間の原因を探す
ブロック? ページング? プリエンプション? 待機?
スレッド間のやり取りの分析
37
コア
表示
コアごとの実行の動作
個々のスレッドは色で識別可能
コア間の移動の統計
使い方
スレッドの親和性や移動による問題
(ガベージ コレクションの発生を見つける)
38
パターン : 直列化
問題 – 並列化されたコードに長期間の同期
サイン – 並列実行されている期間が非常に短い、
あるいは並列実行期間がない、頻繁なブロッキング
解決策 – 競合を起こしているロックを
コール スタックから特定、ロックを削減する
39
パターン : 非均一な負荷の分布
問題 – 負荷の粒度が大きすぎるため並列度が低下
サイン – 関連するスレッドのグループに階段状のパターン
解決策 – 負荷の分割を見直す
40
パターン : 負荷の超過
問題 – 多すぎるスレッドにより CPU の奪い合いが発生
サイン – 実行とプリエンプションの縞模様のパターン
解決策 – 並列度の制限
41
パターン : 非効率な I/O
問題 – 多すぎる同時 I/O が CPU の処理を妨げる
サイン – 実行と I/O 完了待ちが交互に現れる
解決策 – I/O の戦略を見直す。非同期 I/O を検討
42
まとめ
並列化は避けられない課題
TPL を用いることで並列化が容易に
並列ループ、PLINQ による容易な並列化
処理の流れをタスクの関係で表現
強力なプロファイラーを活用
ボトルネックの発見・解消
43
関連セッション
T6-501:多言語パラダイムによる実装手法
動的言語、関数型言語などを含めたプログラミング言語の有効活用へ
T6-309:詳説! Visual Basic 10、C# 4.0 の新機能
~ Visual Studio 2010 で進化したコーディング環境 ~
44
リファレンス
.NET 開発コード サンプル集 Code Recipe
http://msdn.microsoft.com/ja-jp/samplecode.recipe.aspx
第 32 回 並列 (Parallel) で行こう | 連載! とことん C#
http://msdn.microsoft.com/ja-jp/ff849013.aspx
Parallel Programming with Microsoft .NET
http://parallelpatterns.codeplex.com/
書籍 (今秋発売予定): Parallel Programming with Microsoft .NET
Colin Campbell, Ralph Johnson, Ade Miller and Stephen Toub.
45
ご清聴ありがとうございました。
T6-308
アンケートにご協力ください。
© 2010 Microsoft Corporation. All rights reserved. Microsoft, Windows, Windows Vista and other product names are or may be registered trademarks and/or trademarks in the U.S. and/or other countries.
The information herein is for informational purposes only and represents the current view of Microsoft Corporation as of the date of this presentation. Because Microsoft must respond to changing market conditions, it should
not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of any information provided after the date of this presentation. MICROSOFT MAKES NO WARRANTIES, EXPRESS,
IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS PRESENTATION.