Condividi tramite


Procedura: scrivere un ciclo Parallel.For semplice

In questo esempio viene mostrato come utilizzare l'overload più semplice del metodo Parallel.For per calcolare il prodotto di due matrici. Viene inoltre mostrato come utilizzare la classe System.Diagnostics.Stopwatch per confrontare le prestazioni di un ciclo parallelo con quelle di un ciclo non parallelo.

NotaNota

Questa documentazione utilizza espressioni lambda per definire delegati in TPL.Se non si ha familiarità con le espressioni lambda in C# o Visual Basic, vedere Espressioni lambda in PLINQ e TPL.

Esempio

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

}

L'overload più semplice del metodo For può essere utilizzato quando non è necessario annullare le iterazioni o uscire dal relativo ciclo oppure quando non occorre gestire alcuno stato di thread locale.

Quando si parallelizzano elementi di codice, compresi i cicli, un obiettivo importante è utilizzare il più possibile i processori senza tuttavia applicare un parallelismo eccessivo tale che l'overhead dell'elaborazione in parallelo annulli qualsiasi vantaggio in termini di prestazioni. In questo esempio particolare solo il ciclo esterno viene parallelizzato poiché la quantità di lavoro del ciclo interno è alquanto ridotta. La combinazione di una quantità di lavoro ridotta e di effetti di cache indesiderabili può comportare la riduzione delle prestazioni nei cicli paralleli annidati. Pertanto, la parallelizzazione del ciclo esterno è l'unico approccio ottimale per aumentare al massimo i vantaggi della concorrenza nella maggior parte dei sistemi.

Delegato

Il terzo parametro di questo overload di For è un delegato di tipo Action<int> in C# o Action(Of Integer) in Visual Basic. Un delegato Action, indipendentemente se presenta zero, uno o sedici parametri di tipo, restituisce sempre void. In Visual Basic il comportamento di un oggetto Action è definito con un oggetto Sub. Nell'esempio viene utilizzata un'espressione lambda per creare il delegato. Tuttavia, il delegato può essere creato anche in altri modi. Per ulteriori informazioni, vedere Espressioni lambda in PLINQ e TPL.

Valore dell'iterazione

Il delegato accetta un solo parametro di input il cui valore è l'iterazione corrente. Questo valore dell'iterazione viene fornito dal runtime e il relativo valore iniziale è l'indice del primo elemento nel segmento (partizione) dell'origine in fase di elaborazione nel thread corrente.

Se si richiede più controllo sul livello di concorrenza, utilizzare uno degli overload che accetta un parametro di input System.Threading.Tasks.ParallelOptions, ad esempio: Parallel.For(Int32, Int32, ParallelOptions, Action<Int32, ParallelLoopState>).

Valore restituito e gestione delle eccezioni

For restituisce un oggetto System.Threading.Tasks.ParallelLoopResult al completamento di tutti i thread. Questo valore restituito è utile quando si interrompe manualmente l'iterazione del ciclo, poiché ParallelLoopResult archivia informazioni quale l'ultima iterazione che è stata eseguita fino al completamento. Se in uno dei thread si verificano una o più eccezioni verrà generato un oggetto System.AggregateException.

Nel codice di questo esempio il valore restituito di For non viene utilizzato.

Analisi e prestazioni

È possibile utilizzare la Creazione guidata sessione di prestazioni per visualizzare l'utilizzo della CPU nel computer. Come esperimento, aumentare il numero di colonne e di righe nelle matrici. Quanto più grandi sono le matrici, tanto maggiore sarà la differenza di prestazioni fra le versioni in parallelo e sequenziale del calcolo. Quando la matrice presenta dimensioni ridotte l'esecuzione della versione sequenziale risulterà più veloce a causa dell'overhead dovuto alla configurazione del ciclo parallelo.

Le chiamate sincrone a risorse condivise, quali Console o File System, comportano una riduzione significativa delle prestazioni di un ciclo parallelo. Quando si misurano le prestazioni, tentare di evitare chiamate a oggetti come Console.WriteLine all'interno del ciclo.

Compilazione del codice

  • Tagliare e incollare questo codice in un progetto Visual Studio 2010.

Vedere anche

Riferimenti

For

ForEach

Concetti

Parallelismo dei dati (Task Parallel Library)

Programmazione parallela in .NET Framework