連載:C# 4入門

第2回 タスク並列ライブラリ

株式会社ピーデー 川俣 晶
2010/08/20
Page1 Page2 Page3

本当にコアを活用しているの?

 本当にコアを活用しているのか気になる人のために、簡単な検証プログラムを用意した(リスト4)。このプログラムは、単純にループで時間を消費する機能を2回呼び出して時間を計る。ただし、1回目は従来型のシーケンシャルな呼び出しで、2回目はパラレルな呼び出しである。

using System;
using System.Threading.Tasks;

class Program
{
  private const int count = 1000000000;

  private static void taro()
  {
    Console.WriteLine("Taro is busy now");
    for (int i = 0; i < count; i++)
      ;
    Console.WriteLine("Taro Done");
  }

  private static void hanako()
  {
    Console.WriteLine("Hanako is busy now");
    for (int i = 0; i < count; i++)
      ;
    Console.WriteLine("Hanako Done");
  }

  static void Main(string[] args)
  {
    DateTime start1 = DateTime.Now;
    taro();
    hanako();
    Console.WriteLine(DateTime.Now - start1);

    DateTime start2 = DateTime.Now;
    Parallel.Invoke(taro, hanako);
    Console.WriteLine(DateTime.Now - start2);
  }
}
リスト4

Taro is busy now
Taro Done
Hanako is busy now
Hanako Done
00:00:05.9710000
Taro is busy now
Hanako is busy now
Hanako Done
Taro Done
00:00:03.0320000
リスト4の実行結果
デバッグ・ビルドを行い、2コアのCPUで実行。後半の順番は相前後するかもしれない。

 見てのとおり、パラレルに実行させた方が約半分の時間で終わっている。2つのコアに処理が分散されたことが、この時間差から分かるだろう。

 ちなみに、無駄なカウントを行って時間をつぶすことは、8bit CPUの時代には一般的なプログラミング・テクニックであった。しかし、いまでは悪いやり方として避けるべき方法論である。CPUは有限の資源だから、時間を待つだけならThread.Sleepメソッドなどを使い、使用していない時間をほかのプロセスに渡すべきである。しかし、今回は「無駄な仕事をさせることで、処理がコア間で分散していることを示す」ためにあえて良くない方法を使っている。見習うべきお手本ではないので、注意していただきたい。

foreach文をパラレルに

 実際に使用する場合は、foreach文やfor文を並列化することで、即座にパフォーマンスアップできることがある。以下は、foreach文とParallel.ForEachメソッドを比較した例である。

using System;
using System.Threading.Tasks;

class Program
{
  static void Main(string[] args)
  {
    int [] a = {1,2,3,4,5,6,7,8,9};

    foreach (var n in a) Console.Write("{0} ",n);
    Console.WriteLine("by serial");

    Parallel.ForEach(a, (n) => Console.Write("{0} ", n));
    Console.WriteLine("by parallel");
  }
}
リスト5

1 2 3 4 5 6 7 8 9 by serial
1 2 3 4 6 7 8 9 5 by parallel
リスト5の実行結果
後半の順番は不定である。

 ここで注意すべき点は、Parallel.ForEachメソッドの実行結果は、foreach文の実行結果と必ずしも同じではないという点だ。

 もし、順番に意味があればパラレル処理することで狂ってしまうこともあり得る。また、ある値に対する処理が、それまでの値の処理に依存するような場合も処理できない可能性がある。つまり、内容次第で置き換えができないケースもある。パラレルはどれほど簡単にプログラミングできても、実はアルゴリズムの選定から勝負は始まっているのだ。

for文をパラレルに

 単純に数値を数えるfor文も、簡単に並列に置き換えられる。

using System;
using System.Threading.Tasks;

class Program
{
  static void Main(string[] args)
  {
    for (int i = 0; i < 10; i++) Console.Write("{0} ", i);
    Console.WriteLine("by serial");

    Parallel.For(0, 10, (n) => Console.Write("{0} ", n));
    Console.WriteLine("by parallel");
  }
}
リスト6

0 1 2 3 4 5 6 7 8 9 by serial
0 5 1 2 4 8 9 3 6 7 by parallel
リスト6の実行結果
後半の順番は不定である。

 ただし、すべてのfor文をパラレルに置き換えられるわけではない。また、do文やwhile文はパラレルに置き換えられない。繰り返しを始める前に回数が決まっている場合だけ並列化ができる。パラレルに処理するということは、「終わりである」と判定する前に、それ以降の値の処理が走ってしまう可能性があるからである。

 また同じ理由で、break文による繰り返しの打ち切りもできない。打ち切る前に、打ち切った後で処理されないはずの処理が先行して走っていることがあるからである。


 INDEX
  C# 4入門
  第2回 タスク並列ライブラリ
    1.単純に2コアを使う
  2.本当にコアを活用しているの?/foreach文をパラレルに/for文をパラレルに
    3.パラレルへの発想の転換/まとめ
 
インデックス・ページヘ  「C# 4入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間