Delen via


Procedure: Een eenvoudige parallelle.for-lus schrijven

Dit onderwerp bevat twee voorbeelden die de Parallel.For methode illustreren. De eerste gebruikt de overbelasting van de Parallel.For(Int64, Int64, Action<Int64>) methode en de tweede gebruikt de Parallel.For(Int32, Int32, Action<Int32>) overbelasting, de twee eenvoudigste overbelastingen van de Parallel.For methode. U kunt deze twee overbelastingen van de Parallel.For methode gebruiken wanneer u de lus niet hoeft te annuleren, de lusiteraties wilt verbreken of een thread-lokale status hoeft te onderhouden.

Notitie

In deze documentatie worden lambda-expressies gebruikt om gedelegeerden in TPL te definiëren. Als u niet bekend bent met lambda-expressies in C# of Visual Basic, raadpleegt u Lambda-expressies in PLINQ en TPL.

In het eerste voorbeeld wordt de grootte van bestanden in één map berekend. De tweede berekent het product van twee matrices.

Voorbeeld van mapgrootte

Dit voorbeeld is een eenvoudig opdrachtregelprogramma waarmee de totale grootte van bestanden in een map wordt berekend. Er wordt één pad naar één map verwacht als argument en wordt het aantal en de totale grootte van de bestanden in die map gerapporteerd. Nadat u hebt gecontroleerd of de map bestaat, wordt de Parallel.For methode gebruikt om de bestanden in de map op te sommen en de bestandsgrootte te bepalen. Elke bestandsgrootte wordt vervolgens toegevoegd aan de totalSize variabele. Houd er rekening mee dat de toevoeging wordt uitgevoerd door het Interlocked.Add aan te roepen, zodat de toevoeging wordt uitgevoerd als een atomische bewerking. Anders kunnen meerdere taken proberen om de totalSize variabele tegelijkertijd bij te werken.

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main(string[] args)
   {
      long totalSize = 0;
      
      if (args.Length == 0) {
         Console.WriteLine("There are no command line arguments.");
         return;
      }
      if (! Directory.Exists(args[0])) {
         Console.WriteLine("The directory does not exist.");
         return;
      }

      String[] files = Directory.GetFiles(args[0]);
      Parallel.For(0, files.Length,
                   index => { FileInfo fi = new FileInfo(files[index]);
                              long size = fi.Length;
                              Interlocked.Add(ref totalSize, size);
                   } );
      Console.WriteLine("Directory '{0}':", args[0]);
      Console.WriteLine("{0:N0} files, {1:N0} bytes", files.Length, totalSize);
   }
}
// The example displaysoutput like the following:
//       Directory 'c:\windows\':
//       32 files, 6,587,222 bytes
Imports System.IO
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim totalSize As Long = 0

        Dim args() As String = Environment.GetCommandLineArgs()
        If args.Length = 1 Then
            Console.WriteLine("There are no command line arguments.")
            Return
        End If
        If Not Directory.Exists(args(1))
            Console.WriteLine("The directory does not exist.")
            Return
        End If

        Dim files() As String = Directory.GetFiles(args(1))
        Parallel.For(0, files.Length,
                     Sub(index As Integer)
                         Dim fi As New FileInfo(files(index))
                         Dim size As Long = fi.Length
                         Interlocked.Add(totalSize, size)
                     End Sub)
        Console.WriteLine("Directory '{0}':", args(1))
        Console.WriteLine("{0:N0} files, {1:N0} bytes", files.Length, totalSize)
    End Sub
End Module
' The example displays output like the following:
'       Directory 'c:\windows\':
'       32 files, 6,587,222 bytes

Voorbeeld van matrix en stopwatch

In dit voorbeeld wordt de Parallel.For methode gebruikt om het product van twee matrices te berekenen. Ook ziet u hoe u de System.Diagnostics.Stopwatch klasse gebruikt om de prestaties van een parallelle lus te vergelijken met een niet-parallelle lus. Omdat het een grote hoeveelheid uitvoer kan genereren, kan uitvoer in het voorbeeld worden omgeleid naar een bestand.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

class MultiplyMatrices
{
    #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++)
            {
                double temp = 0;
                for (int k = 0; k < matACols; k++)
                {
                    temp += matA[i, k] * matB[k, j];
                }
                result[i, j] += temp;
            }
        }
    }
    #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++)
            {
                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.Error.WriteLine("Executing sequential loop...");
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();

        MultiplyMatricesSequential(m1, m2, result);
        stopwatch.Stop();
        Console.Error.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.Error.WriteLine("Executing parallel loop...");
        stopwatch.Start();
        MultiplyMatricesParallel(m1, m2, result);
        stopwatch.Stop();
        Console.Error.WriteLine("Parallel loop time in milliseconds: {0}",
                                stopwatch.ElapsedMilliseconds);
        OfferToPrint(rowCount, colCount2, result);

        // Keep the console window open in debug mode.
        Console.Error.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.Error.Write("Computation complete. Print results (y/n)? ");
        char c = Console.ReadKey(true).KeyChar;
        Console.Error.WriteLine(c);
        if (Char.ToUpperInvariant(c) == 'Y')
        {
            if (!Console.IsOutputRedirected &&
                RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                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
}
Imports System.Diagnostics
Imports System.Runtime.InteropServices
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
                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
        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
                                          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.Error.WriteLine("Executing sequential loop...")
        Dim stopwatch As New Stopwatch()
        stopwatch.Start()

        MultiplyMatricesSequential(m1, m2, result)
        stopwatch.[Stop]()
        Console.Error.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.Error.WriteLine("Executing parallel loop...")
        stopwatch.Start()
        MultiplyMatricesParallel(m1, m2, result)
        stopwatch.[Stop]()
        Console.Error.WriteLine("Parallel loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds)
        OfferToPrint(rowCount, colCount2, result)

        ' Keep the console window open in debug mode.
        Console.Error.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.Error.Write("Computation complete. Display results (y/n)? ")
        Dim c As Char = Console.ReadKey(True).KeyChar
        Console.Error.WriteLine(c)
        If Char.ToUpperInvariant(c) = "Y"c Then
            If Not Console.IsOutputRedirected AndAlso
                RuntimeInformation.IsOSPlatform(OSPlatform.Windows) 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

Bij het parallelliseren van code, inclusief lussen, is het een belangrijk doel om de processors zoveel mogelijk te gebruiken zonder parallel te lopen naar het punt waar de overhead voor parallelle verwerking eventuele prestatievoordelen afhandelt. In dit specifieke voorbeeld wordt alleen de buitenste lus geparallelliseerd omdat er niet veel werk wordt uitgevoerd in de binnenste lus. De combinatie van een kleine hoeveelheid werk en ongewenste cache-effecten kan leiden tot prestatievermindering in geneste parallelle lussen. Daarom is het parallelliseren van de buitenste lus de beste manier om de voordelen van gelijktijdigheid voor de meeste systemen te maximaliseren.

De gedelegeerde

De derde parameter van deze overbelasting For is een gemachtigde van het type Action<int> C# of Action(Of Integer) in Visual Basic. Een Action gemachtigde, ongeacht of deze nul, één of zestien typeparameters heeft, retourneert altijd ongeldigheid. In Visual Basic wordt het gedrag van een Action bestand gedefinieerd met een Sub. In het voorbeeld wordt een lambda-expressie gebruikt om de gemachtigde te maken, maar u kunt de gemachtigde ook op andere manieren maken. Zie Lambda-expressies in PLINQ en TPL voor meer informatie.

De iteratiewaarde

De gemachtigde gebruikt één invoerparameter waarvan de waarde de huidige iteratie is. Deze iteratiewaarde wordt door de runtime opgegeven en de beginwaarde is de index van het eerste element in het segment (partitie) van de bron die wordt verwerkt op de huidige thread.

Als u meer controle nodig hebt over het gelijktijdigheidsniveau, gebruikt u een van de overbelastingen die een System.Threading.Tasks.ParallelOptions invoerparameter gebruiken, zoals: Parallel.For(Int32, Int32, ParallelOptions, Action<Int32,ParallelLoopState>).

Verwerking van retourwaarde en uitzondering

For retourneert een System.Threading.Tasks.ParallelLoopResult object wanneer alle threads zijn voltooid. Deze retourwaarde is handig wanneer u de herhaling van een lus handmatig stopt of onderbreekt, omdat de ParallelLoopResult informatie zoals de laatste iteratie die tot voltooiing is uitgevoerd, wordt opgeslagen. Als een of meer uitzonderingen op een van de threads voorkomen, wordt er een System.AggregateException gegenereerd.

In de code in dit voorbeeld wordt de retourwaarde For niet gebruikt.

Analyse en prestaties

U kunt de wizard Prestaties gebruiken om het CPU-gebruik op uw computer weer te geven. Verhoog als experiment het aantal kolommen en rijen in de matrices. Hoe groter de matrices, hoe groter het prestatieverschil tussen de parallelle en opeenvolgende versies van de berekening. Wanneer de matrix klein is, wordt de sequentiële versie sneller uitgevoerd vanwege de overhead bij het instellen van de parallelle lus.

Synchrone aanroepen naar gedeelde resources, zoals de console of het bestandssysteem, verminderen de prestaties van een parallelle lus aanzienlijk. Probeer bij het meten van prestaties aanroepen zoals Console.WriteLine binnen de lus te voorkomen.

De code compileren

Kopieer en plak deze code in een Visual Studio-project.

Zie ook