方法: 単純な Parallel.For ループを記述する
この例では、Parallel.For メソッドの最も単純なオーバーロードを使用して、2 つの行列の積を計算します。 また、System.Diagnostics.Stopwatch クラスを使用して、並列ループのパフォーマンスを並列でないループと比較する方法も示します。
![]() |
---|
ここでは、ラムダ式を使用して TPL でデリゲートを定義します。C# または Visual Basic のラムダ式についての情報が必要な場合は、「PLINQ および TPL のラムダ式」を参照してください。 |
使用例
' How to: Write a Simple Parallel.For Loop
Imports System.Threading.Tasks
Module MultiplyMatrices
#Region "Sequential_Loop"
Sub MultiplyMatricesSequential(ByVal matA As Double(,), ByVal matB As Double(,), ByVal result As Double(,))
Dim matACols As Integer = matA.GetLength(1)
Dim matBCols As Integer = matB.GetLength(1)
Dim matARows As Integer = matA.GetLength(0)
For i As Integer = 0 To matARows - 1
For j As Integer = 0 To matBCols - 1
For k As Integer = 0 To matACols - 1
result(i, j) += matA(i, k) * matB(k, j)
Next
Next
Next
End Sub
#End Region
#Region "Parallel_Loop"
Private Sub MultiplyMatricesParallel(ByVal matA As Double(,), ByVal matB As Double(,), ByVal result As Double(,))
Dim matACols As Integer = matA.GetLength(1)
Dim matBCols As Integer = matB.GetLength(1)
Dim matARows As Integer = matA.GetLength(0)
' A basic matrix multiplication.
' Parallelize the outer loop to partition the source array by rows.
Parallel.For(0, matARows, Sub(i)
For j As Integer = 0 To matBCols - 1
' Use a temporary to improve parallel performance.
Dim temp As Double = 0
For k As Integer = 0 To matACols - 1
temp += matA(i, k) * matB(k, j)
Next
result(i, j) += temp
Next
End Sub)
End Sub
#End Region
#Region "Main"
Sub Main(ByVal args As String())
' Set up matrices. Use small values to better view
' result matrix. Increase the counts to see greater
' speedup in the parallel loop vs. the sequential loop.
Dim colCount As Integer = 180
Dim rowCount As Integer = 2000
Dim colCount2 As Integer = 270
Dim m1 As Double(,) = InitializeMatrix(rowCount, colCount)
Dim m2 As Double(,) = InitializeMatrix(colCount, colCount2)
Dim result As Double(,) = New Double(rowCount - 1, colCount2 - 1) {}
' First do the sequential version.
Console.WriteLine("Executing sequential loop...")
Dim stopwatch As New Stopwatch()
stopwatch.Start()
MultiplyMatricesSequential(m1, m2, result)
stopwatch.[Stop]()
Console.WriteLine("Sequential loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds)
' For the skeptics.
OfferToPrint(rowCount, colCount2, result)
' Reset timer and results matrix.
stopwatch.Reset()
result = New Double(rowCount - 1, colCount2 - 1) {}
' Do the parallel loop.
Console.WriteLine("Executing parallel loop...")
stopwatch.Start()
MultiplyMatricesParallel(m1, m2, result)
stopwatch.[Stop]()
Console.WriteLine("Parallel loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds)
OfferToPrint(rowCount, colCount2, result)
' Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
#End Region
#Region "Helper_Methods"
Function InitializeMatrix(ByVal rows As Integer, ByVal cols As Integer) As Double(,)
Dim matrix As Double(,) = New Double(rows - 1, cols - 1) {}
Dim r As New Random()
For i As Integer = 0 To rows - 1
For j As Integer = 0 To cols - 1
matrix(i, j) = r.[Next](100)
Next
Next
Return matrix
End Function
Sub OfferToPrint(ByVal rowCount As Integer, ByVal colCount As Integer, ByVal matrix As Double(,))
Console.WriteLine("Computation complete. Print results? y/n")
Dim c As Char = Console.ReadKey().KeyChar
If c = "y"c OrElse c = "Y"c Then
Console.WindowWidth = 168
Console.WriteLine()
For x As Integer = 0 To rowCount - 1
Console.WriteLine("ROW {0}: ", x)
For y As Integer = 0 To colCount - 1
Console.Write("{0:#.##} ", matrix(x, y))
Next
Console.WriteLine()
Next
End If
End Sub
#End Region
End Module
namespace MultiplyMatrices
{
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
class Program
{
#region Sequential_Loop
static void MultiplyMatricesSequential(double[,] matA, double[,] matB,
double[,] result)
{
int matACols = matA.GetLength(1);
int matBCols = matB.GetLength(1);
int matARows = matA.GetLength(0);
for (int i = 0; i < matARows; i++)
{
for (int j = 0; j < matBCols; j++)
{
for (int k = 0; k < matACols; k++)
{
result[i, j] += matA[i, k] * matB[k, j];
}
}
}
}
#endregion
#region Parallel_Loop
static void MultiplyMatricesParallel(double[,] matA, double[,] matB, double[,] result)
{
int matACols = matA.GetLength(1);
int matBCols = matB.GetLength(1);
int matARows = matA.GetLength(0);
// A basic matrix multiplication.
// Parallelize the outer loop to partition the source array by rows.
Parallel.For(0, matARows, i =>
{
for (int j = 0; j < matBCols; j++)
{
// Use a temporary to improve parallel performance.
double temp = 0;
for (int k = 0; k < matACols; k++)
{
temp += matA[i, k] * matB[k, j];
}
result[i, j] = temp;
}
}); // Parallel.For
}
#endregion
#region Main
static void Main(string[] args)
{
// Set up matrices. Use small values to better view
// result matrix. Increase the counts to see greater
// speedup in the parallel loop vs. the sequential loop.
int colCount = 180;
int rowCount = 2000;
int colCount2 = 270;
double[,] m1 = InitializeMatrix(rowCount, colCount);
double[,] m2 = InitializeMatrix(colCount, colCount2);
double[,] result = new double[rowCount, colCount2];
// First do the sequential version.
Console.WriteLine("Executing sequential loop...");
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
MultiplyMatricesSequential(m1, m2, result);
stopwatch.Stop();
Console.WriteLine("Sequential loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds);
// For the skeptics.
OfferToPrint(rowCount, colCount2, result);
// Reset timer and results matrix.
stopwatch.Reset();
result = new double[rowCount, colCount2];
// Do the parallel loop.
Console.WriteLine("Executing parallel loop...");
stopwatch.Start();
MultiplyMatricesParallel(m1, m2, result);
stopwatch.Stop();
Console.WriteLine("Parallel loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds);
OfferToPrint(rowCount, colCount2, result);
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
#endregion
#region Helper_Methods
static double[,] InitializeMatrix(int rows, int cols)
{
double[,] matrix = new double[rows, cols];
Random r = new Random();
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
matrix[i, j] = r.Next(100);
}
}
return matrix;
}
private static void OfferToPrint(int rowCount, int colCount, double[,] matrix)
{
Console.WriteLine("Computation complete. Print results? y/n");
char c = Console.ReadKey().KeyChar;
if (c == 'y' || c == 'Y')
{
Console.WindowWidth = 180;
Console.WriteLine();
for (int x = 0; x < rowCount; x++)
{
Console.WriteLine("ROW {0}: ", x);
for (int y = 0; y < colCount; y++)
{
Console.Write("{0:#.##} ", matrix[x, y]);
}
Console.WriteLine();
}
}
}
#endregion
}
}
反復処理を取り消したり中断する必要がない場合、またはスレッド ローカルの状態を維持する必要がない場合は、For メソッドの最も基本的なオーバーロードを使用できます。
ループも含めて、コードを並列化する場合に重要なことは、並列処理のオーバーヘッドがパフォーマンスの低下を引き起こさないよう、過剰に並列化せずにできる限りプロセッサを利用することです。 この例では、外側のループのみが並列化されます。内側のループには実行される作業がそれほど多くないためです。 作業量が少ない場合でも、不適切なキャッシュ処理と組み合わせると、入れ子の並列ループでパフォーマンスが低下するおそれがあります。 このため、大部分のシステムでは、外側のループだけを並列化することで、最大限の効果を得ることができます。
デリゲート
For のこのオーバーロードの 3 つ目のパラメーターは、C# では Action<int>、Visual Basic では Action(Of Integer) の型のデリゲートです。 Action デリゲートは、ゼロ、1、または 16 の型パラメーターを持つかどうかに関係なく、常に void を返します。 Visual Basic では、Action の動作は Sub で定義されています。 この例ではラムダ式を使用してデリゲートを作成しますが、他の方法でデリゲートを作成することもできます。 詳細については、「PLINQ および TPL のラムダ式」を参照してください。
反復値
デリゲートは、値が現在の反復処理である単一の入力パラメーターを取得します。 反復値は実行時に入力され、その開始値は、現在のスレッドで処理されているソースのセグメント (パーティション) 上の最初の要素のインデックスです。
同時実行のレベルでさらに制御する必要がある場合は、Parallel.For(Int32, Int32, ParallelOptions, Action<Int32, ParallelLoopState>) などの System.Threading.Tasks.ParallelOptions 入力パラメーターを受け取るオーバーロードを使用します。
戻り値と例外処理
すべてのスレッドが完了すると、For は System.Threading.Tasks.ParallelLoopResult オブジェクトを返します。 この戻り値は、ループ反復を手動で停止または中断している場合に便利です。ParallelLoopResult には、完了まで実行された最後の反復処理などの情報が格納されているためです。 スレッドのいずれかで 1 つ以上の例外が発生した場合は、System.AggregateException がスローされます。
この例では、For の戻り値は使用されません。
分析とパフォーマンス
パフォーマンス ウィザードを使用すると、コンピューターで CPU 使用率を表示できます。 試しに、行列の列と行の数を増やしてみてください。 行列が大きくなると、並列計算と順次計算とのパフォーマンスの差が広がります。 行列が小さいと、並列ループの設定によるオーバーヘッドによって順次計算は速くなります。
コンソールやファイル システムなど、共有リソースへの非同期呼び出しでは、並列ループのパフォーマンスが著しく低下します。 パフォーマンスを計測するときは、ループ内で Console.WriteLine などの呼び出しを行わないようにしてください。
コードのコンパイル
- このコードをコピーして Visual Studio 2010 プロジェクトに貼り付けます。