Gewusst wie: Schreiben einer einfachen Parallel.For-Schleife

In diesem Beispiel wird gezeigt, wie Sie die einfachste Überladung der Parallel.For-Methode verwenden, um das Produkt aus zwei Matrizen zu berechnen. Es zeigt weiterhin, wie Sie mithilfe der System.Diagnostics.Stopwatch-Klasse die Leistung einer parallelen Schleife mit der einer nicht parallelen Schleife vergleichen.

HinweisHinweis

In dieser Dokumentation werden Delegaten in TPL mithilfe von Lambda-Ausdrücken definiert.Falls Sie nicht mit der Verwendung von Lambda-Ausdrücken in C# oder Visual Basic vertraut sind, finden Sie entsprechende Informationen unter Lambda-Ausdrücke in PLINQ und TPL.

Beispiel

' 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
    }

}

Sie können die einfachste Überladung der For-Methode verwenden, wenn Sie die Iterationen nicht abbrechen bzw. unterbrechen oder keinen lokalen Zustand eines Threads beibehalten müssen.

Beim Parallelisieren von beliebigem Code, einschließlich Schleifen, besteht ein wichtiges Ziel darin, die Prozessorleistung möglichst umfassend auszuschöpfen, ohne dass der Punkt erreicht wird, an dem der mit der parallelen Verarbeitung verbundene Mehraufwand die Leistungsvorteile negiert. In diesem konkreten Beispiel wird nur die äußere Schleife parallelisiert, da in der inneren Schleife nur wenige Aufgaben ausgeführt werden. Die Kombination aus wenigen Aufgaben und unerwünschten Auswirkungen auf den Cache können in geschachtelten parallelen Schleifen zu Leistungseinbußen führen. Daher ist eine Parallelisierung der äußeren Schleife die beste Möglichkeit, die Vorteile der Parallelität in den meisten Systemen zu maximieren.

Der Delegat

Der dritte Parameter dieser Überladung von For ist ein Delegat vom Typ Action<int> in C# oder Action(Of Integer) in Visual Basic. Ein Action-Delegat gibt immer "void" zurück, egal ob er keinen, einen oder sechzehn Typparameter aufweist. In Visual Basic wird das Verhalten von Action mit Sub definiert. Im Beispiel wird der Delegat mithilfe eines Lambda-Ausdrucks erstellt, Sie können den Delegaten jedoch auch auf die andere Weise erstellen. Weitere Informationen finden Sie unter Lambda-Ausdrücke in PLINQ und TPL.

Der Iterationswert

Der Delegat verwendet einen einzelnen Eingabeparameter, dessen Wert der aktuellen Iteration entspricht. Dieser Iterationswert wird von der Laufzeit bereitgestellt, und sein Startwert ist der Index des ersten Elements im Segment (Partition) der Quelle, die im aktuellen Thread verarbeitet wird.

Wenn Sie mehr Kontrolle über die Parallelitätsebene benötigen, verwenden Sie eine der Überladungen, die einen System.Threading.Tasks.ParallelOptions-Eingabeparameter nutzen, z. B. Parallel.For(Int32, Int32, ParallelOptions, Action<Int32, ParallelLoopState>).

Rückgabewert und Ausnahmebehandlung

For gibt ein System.Threading.Tasks.ParallelLoopResult-Objekt zurück, nachdem alle Threads abgeschlossen wurden. Dieser Rückgabewert ist hilfreich, wenn Sie die Schleifeniteration manuell anhalten oder unterbrechen, da ParallelLoopResult Informationen speichert, z. B. die letzte Iteration, die vollständig ausgeführt wurde. Wenn eine oder mehrere Ausnahmen in einem der Threads auftreten, wird eine System.AggregateException ausgelöst.

Im Code in diesem Beispiel wird der Rückgabewert von For nicht verwendet.

Analyse und Leistung

Verwenden Sie zum Anzeigen der CPU-Auslastung auf Ihrem Computer den Leistungs-Assistenten. Vergrößern Sie zur Probe die Anzahl von Spalten und Zeilen in den Matrizen. Je größer die Matrizen sind, umso größer ist der Leistungsunterschied zwischen der parallelen und sequenziellen Version der Berechnung. Bei einer kleinen Matrix wird die sequenzielle Version aufgrund des Aufwands, der mit der Einrichtung der parallelen Schleife verbunden ist, schneller ausgeführt.

Synchrone Aufrufe freigegebener Ressourcen, wie der Konsole oder des Dateisystems, beeinträchtigen die Leistung einer parallelen Schleife in erheblichem Maße. Vermeiden Sie beim Messen der Leistung Aufrufe wie Console.WriteLine innerhalb der Schleife.

Kompilieren des Codes

  • Schneiden Sie diesen Code aus, und fügen Sie ihn in ein Visual Studio 2010-Projekt ein.

Siehe auch

Referenz

For

ForEach

Konzepte

Datenparallelität (Task Parallel Library)

Parallele Programmierung in .NET Framework