HOW TO:撰寫簡單的 Parallel.For 迴圈

此範例說明如何使用 Parallel.For 方法最簡單的多載,計算兩個矩陣的乘積。 另外也會說明如何使用 System.Diagnostics.Stopwatch 類別比較平行迴圈與非平行迴圈的效能。

注意事項注意事項

本文件使用 Lambda 運算式來定義 TPL 中的委派。如果您不太熟悉 C# 或 Visual Basic 中的 Lambda 運算式,請參閱 PLINQ 和 TPL 中的 Lambda 運算式

範例

' 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 方法最基本的多載。

平行處理任何程式碼時 (包括迴圈) 的其中一個重要目標是盡可能使用處理器,同時使平行處理的負荷程度不致超過而對效能產生負面影響。 在此特定範例中只會平行處理外部迴圈,因為在內部迴圈中執行的工作並不多。 如果工作量很少,而快取效果又不理想,將可能導致巢狀平行迴圈的效能降低。 因此,在大部分的系統上,僅平行處理外部迴圈是充分發揮並行優勢的最佳途徑。

委派

在 C# 中,For 的這個多載的第三個參數為型別 Action<int> 的委派 (在 Visual Basic 中為 Action(Of Integer))。 Action 委派無論具有零個、一個還是十六個型別參數,一律會傳回 void。 在 Visual Basic 中,Action 的行為是以 Sub 定義的。 範例使用 Lambda 運算式建立委派,但您可以用其他方式建立委派。 如需詳細資訊,請參閱PLINQ 和 TPL 中的 Lambda 運算式

反覆項目值

委派會採用具有目前反覆項目值的單一輸入參數。 此反覆項目值由執行階段提供,其起始值為目前執行緒上正在處理之來源區段 (磁碟分割) 上第一個項目的索引。

如果您需要進一步控制並行層級,請使用其中一個採用 System.Threading.Tasks.ParallelOptions 輸入參數的多載,例如:Parallel.For(Int32, Int32, ParallelOptions, Action<Int32, ParallelLoopState>)

傳回值和例外狀況處理

For 會在所有執行緒皆完成時傳回 System.Threading.Tasks.ParallelLoopResult 物件。 此傳回值在您手動停止或中斷迴圈反覆運算時有其用處,因為 ParallelLoopResult 會儲存完成執行的最後一次反覆運算之類的資訊。 如果有其中一個執行緒上發生一個或多個例外狀況,則會擲回 System.AggregateException

在此範例的程式碼中,不會使用 For 的傳回值。

分析和效能

您可以使用「效能精靈」檢視電腦上的 CPU 使用量。 請試著增加矩陣中的資料行和資料列數目。 矩陣越大,平行和循序計算之間的效能差異就越大。 當矩陣較小時,循序計算的執行速度因為設定平行迴圈有額外負荷所以較快。

同步呼叫主控台或檔案系統之類的共用資源時,會大幅降低平行迴圈的效能。 評估效能時,請避免在迴圈內呼叫 Console.WriteLine 之類的項目。

編譯程式碼

  • 將此程式碼剪下並貼到 Visual Studio 2010 專案中。

請參閱

參考

For

ForEach

概念

資料平行處理原則 (工作平行程式庫)

以 .NET Framework 進行平行程式設計