更新日: 2010 年 8 月 6 日

執筆者: 株式会社ピーデー 川俣 晶

この記事は、「MSDN プログラミング シリーズ」として発行している技術書籍「プロフェッショナルマスター Visual C# 2010 ~最新テクニックをマスターする 35 のテーマ」(日経 BP 社刊) を基にワンポイント テクニックを紹介しています。


センセーション 「おほん、教師のセンセーションである」
ジョシュア 「生徒のジョシュアです。ところで、トレース実行していると関係ない行にいきなり飛んでいきます。デバッガーのバグですか?」
センセーション 「もしや、パラレルなど、複数スレッドが生成される機能を使っておらんか?」
ジョシュア 「どうして分かるんですか?」
センセーション 「そういう場合はトレースしたいスレッドだけ残して後は凍結じゃ!」

目次

  1. タスクごとの実行状況
  2. 凍結とトレース
  3. デッドロックの検出
  4. 並列処理のプロファイル
  5. 結末

1. タスクごとの実行状況

Visual Studio では従来からマルチスレッド対応のデバッグ機能を持ちますが、Visual Studio 2010 では、並列タスクというウィンドウを開くことができます。メニューから [デバッグ] - [ウィンドウ] - [並列タスク] を選択します。

例えば、以下の図は、次のようなプログラムで、ラムダ式の内部にブレークポイントを設定して実行した例です。

C#
using System;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        Parallel.For(0, 10, (i) => Console.WriteLine(i));
    }
}
 

図 1

これを使えば、並列処理がどういうタスクに分散されているか一目瞭然です。

また、メニューから [デバッグ] - [ウィンドウ] - [並列スタック] を選択して別のウィンドウを開くこともできます。これを使うと、「第 32 回 並列 (Parallel) で行こう」で説明したタスクの親子関係など、個々のスレッドの関係とそれぞれのスタック トレースを即座に把握できます。(また、この画面から、別のスレッドに移動して現在の状況を見ることもできます。)

図 2

ページのトップへ


2. 凍結とトレース

トレース実行しているプログラムで、いきなりトレースの位置が飛んでしまうことは、マルチスレッドのプログラムでは普通にあり得ます。例えば、排他制御や同期入出力などで現在デバッグ中のスレッドが停止した場合、実行可能状態の別のスレッドが実行できるためにそこに飛んでしまうためで、これは、プログラムとしてはごく自然な動きです。

しかし、特定のスレッドに集中してトレース実行したい場合、この動きは邪魔になってしまいます。そこで、もし、スレッド間でリソースの競合などがないならば、先ほどの並列タスクの画面で、トレース対象でないスレッドを凍結して、特定のスレッドに集中してトレースをすることができます。(リソースが競合する場合などは、デッドロックなどの原因となるため、むやみに使用しないでください。)

図 3

凍結されたスレッドは、切り替わって実行されることはありません。これで、1 つのスレッドに絞り込んで、動作を追跡することができます。Parallel.Invoke のようなメソッドを使用して並列処理を行う場合、必須のデバッグ テクニックと言えるでしょう。

Note: なお、上図では、厳密に、プログラム中のすべてのスレッドが表示されているわけではありません。例えば、ガベージ コレクションやユーザー インタフェースなどの重要なスレッドは除外され、デバッグに必要なワーカー スレッドのみが一覧で表示されます。

ページのトップへ


3. デッドロックの検出

並列処理を行う場合、どうしても同期という問題を避けて通れません。アクセスが競合すると破壊されてしまうデータがあるため、それを回避するために、「互いに譲り合う」必要があります。しかし、この「譲り合い」は、1 つ間違うとデッドロックという問題を発生させます。

以下はデッドロックが発生する可能性があるコードの例です。1 つの処理が a をロックして b を取りに行き、同時に別の処理が b をロックして a を取りに行こうとすると、双方はお互いのロックを解除できず、永遠に待ち続けることになります。

C#
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        while (true)
        {
            object a = new object();
            object b = new object();
            Parallel.Invoke(
              () => TryIt(a, b),
              () => TryIt(b, a));
        }
    }
    static void TryIt(object one, object two)
    {
        lock (one)
        {
            lock (two)
            {
            }
        }
    }
}
 

このような状況で、Visual Studio 上で一時停止すると、上述した [並列タスク] のペイン (画面) で、デッドロックの検出が報告され、「待機中デッドロック」と表示してくれます。

図 4

このような問題を解消する方法は一概には言えません (ケースに応じてさまざまな手法があります) が、例えば、複数のリソースをロックする場合は、他のロックを解除してからロックをおこなうなど、多くの場合、状況にあわせたプログラムの修正が必要になります。

ページのトップへ


4. 並列処理のプロファイル

並列処理に関する支援はデバッグだけではありません。プロファイリングに関する支援も受けることができます。

Visual Studio 2010 Premium または Ultimate で提供されているプロファイラーの機能は、このための支援を提供します。「実際に実行してみて、どうであったのか」という実行結果を容易に把握して、ボトルネックや性能上の問題を見つけ、性能向上を計るヒントとして役立てることができます。

プロファイラーを使用することは簡単です。あらかじめ、Visual Studio を管理者権限で起動しておき、[分析] - [パフォーマンス ウィザードの起動] メニューを選択すると画面が表示されます。この画面で、[同時実行] を選択して、[リソース競合データを収集]、[マルチスレッド アプリケーションの動作を視覚化] の双方にチェックを付けて、[次へ] ボタンを押し、つぎの画面 (ウィザード) で、プロファイリングをおこなう (Visual Studio の) プロジェクトを選択して、 [完了] ボタンを押します。

すると、プロファイラーが開始され、プログラム (上記で選択したプロジェクト) が実行されて、以下の画面が表示されます。(シンボルの読み込みなどをおこなうため、画面が表示されるまで、しばらく時間がかかります。)

図 5

あとは、さまざまな視点からコード分析をおこなうだけです。上図で、「CPU 使用状況」、「スレッド」、「コア」の 3 つのアイコンが表示されていますが、これらの視点でプロファイリングの情報を分析できます。さいごに、これら 3 つの情報を簡単に紹介しましょう。

まずは、「CPU 使用状況」(下図) です。

図

この画面をズームすると下記のように表示され、このグラフから、ピーク時は 1 つ以上のコアを消費しているものの、まだ CPU を利用できる可能性があることなどが予想されます。

図 7

次は、「スレッド」です。(下図)

図 8

これを見ると、個々のワーカー スレッドが同期のために使用されており (上記の朱色部分)、多数のスレッドが実行を待機しているか、あるいは同期の問題により改善の余地が大きいことなどが予測できます。

最後は「コア」です。(下図)

図 9

これを見ると、どのスレッドがどのコア上で実行されているかが分かります。一般に、スレッドがコアを超えて頻繁にコンテキスト スイッチされると、パフォーマンスに影響をあたえることがあり、こうしたチューニングをおこなう場合には、この「コア」のプロファイル情報を参考にすることが可能です。

これらの情報を活用して、いかに改善すべきボトルネックを見いだしていくかは C# プログラマーである皆さんの役目です。Visual Studio は、あくまでも、そのための参考情報を提示してくれるだけで、答えを提示しているわけではありません。

メニーコア時代を目前に控え、それを乗りこなす準備ができてきました。

ページのトップへ


5. 結末

ジョシュア 「分かりました。いろいろな原因と可能性に対し、こうしたさまざまな機能が活用できるわけですね!」
センセーション 「うむ。デバッグ作業は、プログラミングとは切っても切れない関係にある。並列化というプログラミング側の機能がパワーアップすれば、それにあわせて、デバッグ機能もパワーアップされているというわけじゃ」
ジョシュア 「はい。これを使いこなして、さっそく、パラレルを使ったプログラムのバグ退治に行ってきます!」
センセーション 「ジョシュアも、もう『助手』は卒業じゃな...」

Code Recipe Code Recipe

ページのトップへ