2013-06-14 13 views
6

並列処理を使用していくつかのネストされたループを高速化しようとしていますが、構文を正しく取得できません。私は、ビットマップ内のいくつのピクセルが赤、白、または黒であるかのカウントを取得しようとしています。これは、別の場所で列挙型の値です。Parallel for C#で共有変数を使用する

:私は、以下のように共有変数を更新マイクロソフト社とstackoverflowのからのループの並列のコードを見てきた

 Bitmap image = new Bitmap(@"Input.png"); 
     var colourCount = new int[3]; 

     for (var x = 0; x < image.Width; x++) 
     { 
      for (var y = 0; y < image.Height; y++) 
      { 
       switch (image.GetPixel(x, y).ToArgb()) 
       { 
        case (int)colours.red: colourCount[0]++; break; 
        case (int)colours.white: colourCount[1]++; break; 
        case (int)colours.black: colourCount[2]++; break; 
        default: throw new ArgumentOutOfRangeException(string.Format("Unexpected colour found: '{0}'", image.GetPixel(x, y).ToArgb())); 
       } 
      } 
     } 

:シリアル処理で

Iが正常に動作し、次のコードを有します

 Parallel.For<int>(0, result.Count,() => 0, (i, loop, subtotal) => 
     { 
      subtotal += result[i]; 
      return subtotal; 
     }, 
      (x) => Interlocked.Add(ref sum, x) 
     ); 

しかし、すべての例では、共有変数としてint型などの単純型を使用しています。サイズ3の配列に書き込む構文はわかりません。私はこのすべてに間違っているのだろうか?

ところで、私はGetPixelがBitmap.LockBitsのようなものに比べて非常に遅いことをパフォーマンスの面で知っています。私はちょうどパラレルループの原理を正しいものにしようとしています。

答えて

4

Parallel.Forのオーバーロードを使用すると、スレッドローカル状態を維持できます。この場合、生成されるスレッドごとにint[3]の配列を作成します。並列ループの各反復では、ローカルアレイlocalColourCountのみを更新します。最後に、スレッドをリタイアするときは、各ローカル配列の結果をグローバルな配列colourCountに集約します。ただし、これは共有データ構造であるため、アクセスしながら相互排除を強制します。

Bitmap image = new Bitmap(@"Input.png"); 
var colourCount = new int[3]; 

Parallel.For(0, image.Width, 

    // localInit: The function delegate that returns the initial state 
    //   of the local data for each task. 
    () => new int[3], 

    // body: The delegate that is invoked once per iteration. 
    (int x, ParallelLoopState state, int[] localColourCount) => 
    { 
     for (var y = 0; y < image.Height; y++) 
     { 
      switch (image.GetPixel(x, y).ToArgb()) 
      { 
       case (int)colours.red: localColourCount[0]++; break; 
       case (int)colours.white: localColourCount[1]++; break; 
       case (int)colours.black: localColourCount[2]++; break; 
       default: throw new ArgumentOutOfRangeException(
          string.Format("Unexpected colour found: '{0}'", 
          image.GetPixel(x, y).ToArgb())); 
      } 
     } 
    }, 

    // localFinally: The delegate that performs a final action 
    //    on the local state of each task. 
    (int[] localColourCount) => 
    { 
     // Accessing shared variable; synchronize access. 
     lock (colourCount) 
     { 
      for (int i = 0; i < 3; ++i) 
       colourCount[i] += localColourCount[i]; 
     } 
    }); 

このコードはBitmap.GetPixelに又は場合であってもなくてもよい、スレッドセーフであると仮定しています。

注意すべき点は、ArgumentOutOfRangeExceptionインスタンスはすべてAggregateExceptionに結合されるため、エラー処理コードを調整する必要があることです。

+0

実際には 'localFinally'に' lock'は必要ありません。代わりに、あなたのループで 'Interlocked.Add()'を使うことができます。 'lock'を使うともっと明白に正しいものになります。 – svick

+0

@svick: 'Interlocked.Add'の複数の呼び出しは、単一の' lock'より効率が悪いかもしれません。 (「colourCount」が数十の要素で構成されていれば間違いありませんが、3の確信はありません。)[Albahari](http://www.albahari.com/threading/part4.aspx):「すべての''のメソッドは完全なフェンスを生成します。 "反復キャッシュ無効化オーバーヘッドは、単一ロックのコストを超える可能性があります。 – Douglas

+0

これを投稿していただきありがとうございます。 GetPixelがスレッドセーフではないことを示唆しているのは間違いありません。実際には画像のプロパティやメソッドにアクセスするのはノーではないようです。だから私はそれを理解する必要があります。ループ自体が正常に動作しています。再度、感謝します! –

関連する問題