Partager via


Comment : écrire une boucle Parallel.For simple

Cet exemple indique comment utiliser la surcharge la plus simple de la méthode Parallel.For pour calculer le produit de deux matrices. Il indique également comment utiliser la classe System.Diagnostics.Stopwatch pour comparer les performances d'une boucle parallèle et d'une boucle non parallèle.

RemarqueRemarque

Cette documentation utilise des expressions lambda pour définir des délégués dans la bibliothèque parallèle de tâches.Si vous n'êtes pas familiarisé avec les expressions lambda en C# ou Visual Basic, consultez Expressions lambda en PLINQ et dans la bibliothèque parallèle de tâches.

Exemple

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

}

Vous pouvez utiliser la surcharge la plus basique de la méthode For lorsque vous n'avez pas besoin d'annuler ni de quitter des itérations ou de maintenir un état local de thread.

Lorsque vous parallélisez du code, notamment des boucles, il est important d'utiliser autant que possible les processeurs sans pour cela surparalléliser, c'est-à-dire en vous arrêtant avant que la surcharge du traitement en parallèle ne réduise les performances. Dans cet exemple, seule la boucle externe est parallélisée car le nombre de travaux exécutés dans la boucle interne est réduit. La combinaison d'une petite quantité de travail et d'effets indésirables de cache peut provoquer une diminution des performances dans les boucles parallèles imbriquées. Par conséquent, paralléliser uniquement la boucle externe est le meilleur moyen d'optimiser les avantages offerts par l'accès concurrentiel sur la plupart des systèmes.

Délégué

Le troisième paramètre de cette surcharge de For est un délégué de type Action<int> en C# ou Action(Of Integer) en Visual Basic. Un délégué Action, quel que soit le nombre de paramètres de type, retourne toujours void. En Visual Basic, le comportement d'un Action est défini avec un Sub. L'exemple utilise une expression lambda pour créer le délégué, mais vous pouvez créer le délégué autrement. Pour plus d'informations, consultez Expressions lambda en PLINQ et dans la bibliothèque parallèle de tâches.

Valeur d'itération

Le délégué prend un paramètre d'entrée unique dont la valeur est l'itération actuelle. Cette valeur d'itération est fournie par l'exécution, et sa valeur de départ est l'index du premier élément du segment (partition) de la source traitée sur le thread actuel.

Si vous avez besoin de davantage de contrôle sur le niveau d'accès concurrentiel, utilisez l'une des surcharges qui prend un paramètre d'entrée System.Threading.Tasks.ParallelOptions, telles que : Parallel.For(Int32, Int32, ParallelOptions, Action<Int32, ParallelLoopState>).

Valeur de retour et gestion des exceptions

For retourne un objet System.Threading.Tasks.ParallelLoopResult lorsque tous les threads sont terminés. Cette valeur de retour est utile lorsque vous arrêtez ou quittez une itération de boucle manuellement, parce que le ParallelLoopResult stocke des informations telles que la dernière itération dont l'exécution est terminée. Si une ou plusieurs exceptions se produisent sur l'un des threads, une System.AggregateException sera levée.

Dans le code de cet exemple, la valeur de retour For n'est pas utilisée.

Analyse et performance

Vous pouvez utiliser l'Assistant Performance pour consulter l'utilisation du processeur de votre ordinateur. À titre d'expérimentation, augmentez le nombre de colonnes et de lignes des matrices. Plus les matrices sont grandes, plus la différence de performance est grande pour les versions de calcul parallèles et séquentielles. Lorsque la matrice est petite, la version séquentielle s'exécutera plus vite à cause de la surcharge due à l'installation de la boucle parallèle.

Les appels synchrones aux ressources partagées, telles que la Console ou le Système de fichiers, diminueront considérablement les performances d'une boucle parallèle. Lorsque vous mesurez les performances, essayez d'éviter les appels tels que Console.WriteLine dans la boucle.

Compilation du code

  • Copiez et collez ce code dans un projet Visual Studio 2010.

Voir aussi

Référence

For

ForEach

Concepts

Parallélisme de données (bibliothèque parallèle de tâches)

Programmation parallèle dans le .NET Framework