Udostępnij za pośrednictwem


Porady: zapisywanie prostej równoległej pętli For

Ten temat zawiera dwa przykłady ilustrujące metodę Parallel.For . Pierwszy używa Parallel.For(Int64, Int64, Action<Int64>) przeciążenia metody, a drugi używa Parallel.For(Int32, Int32, Action<Int32>) przeciążenia, dwa najprostsze Parallel.For przeciążenia metody. Można użyć tych dwóch przeciążeń Parallel.For metody, gdy nie trzeba anulować pętli, przerwać iteracji pętli lub zachować dowolny stan lokalny wątku.

Uwaga

Ta dokumentacja używa wyrażeń lambda do definiowania delegatów w TPL. Jeśli nie znasz wyrażeń lambda w języku C# lub Visual Basic, zobacz Wyrażenia lambda w plINQ i TPL.

Pierwszy przykład oblicza rozmiar plików w jednym katalogu. Drugi oblicza produkt dwóch macierzy.

Przykład rozmiaru katalogu

W tym przykładzie jest proste narzędzie wiersza polecenia, które oblicza całkowity rozmiar plików w katalogu. Oczekuje pojedynczej ścieżki katalogu jako argumentu i zgłasza liczbę i całkowity rozmiar plików w tym katalogu. Po sprawdzeniu, czy katalog istnieje, użyje Parallel.For metody , aby wyliczyć pliki w katalogu i określić ich rozmiary plików. Każdy rozmiar pliku jest następnie dodawany do zmiennej totalSize . Należy pamiętać, że dodanie jest wykonywane przez wywołanie Interlocked.Add metody tak, aby dodanie było wykonywane jako operacja niepodzielna. W przeciwnym razie wiele zadań może spróbować zaktualizować zmienną totalSize jednocześnie.

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

Przykład macierzy i stopera

W tym przykładzie użyto Parallel.For metody do obliczenia produktu dwóch macierzy. Pokazano również, jak używać System.Diagnostics.Stopwatch klasy do porównywania wydajności pętli równoległej z pętlą nie równoległą. Należy pamiętać, że ponieważ może wygenerować dużą ilość danych wyjściowych, przykład umożliwia przekierowanie danych wyjściowych do pliku.

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

W przypadku równoległości dowolnego kodu, w tym pętli, jednym z ważnych celów jest wykorzystanie procesorów tak bardzo, jak to możliwe bez konieczności równoległości do punktu, w którym obciążenie związane z przetwarzaniem równoległym neguje wszelkie korzyści z wydajności. W tym konkretnym przykładzie tylko pętla zewnętrzna jest równoległa, ponieważ w pętli wewnętrznej nie wykonano zbyt wiele pracy. Połączenie niewielkiej ilości pracy i niepożądanych efektów pamięci podręcznej może spowodować obniżenie wydajności w zagnieżdżonych pętlach równoległych. W związku z tym równoległość pętli zewnętrznej jest najlepszym sposobem zmaksymalizowania korzyści związanych z współbieżnością w większości systemów.

Delegat

Trzeci parametr tego przeciążenia For to delegat typu Action<int> w języku C# lub Action(Of Integer) Visual Basic. Delegat Action , niezależnie od tego, czy ma zero, jeden lub szesnaście parametrów typu, zawsze zwraca wartość void. W języku Visual Basic zachowanie obiektu Action jest definiowane za pomocą elementu Sub. W przykładzie użyto wyrażenia lambda do utworzenia delegata, ale możesz również utworzyć delegata na inne sposoby. Aby uzyskać więcej informacji, zobacz Wyrażenia lambda w PLINQ i TPL.

Wartość iteracji

Delegat przyjmuje jeden parametr wejściowy, którego wartość jest bieżącą iteracją. Ta wartość iteracji jest dostarczana przez środowisko uruchomieniowe, a jego wartość początkowa to indeks pierwszego elementu w segmencie (partycji) źródła przetwarzanego w bieżącym wątku.

Jeśli potrzebujesz większej kontroli nad poziomem współbieżności, użyj jednego z przeciążeń, które przyjmuje System.Threading.Tasks.ParallelOptions parametr wejściowy, na przykład: Parallel.For(Int32, Int32, ParallelOptions, Action<Int32,ParallelLoopState>).

Zwracana wartość i obsługa wyjątków

For Funkcja zwraca obiekt, System.Threading.Tasks.ParallelLoopResult gdy wszystkie wątki zostały ukończone. Ta wartość zwracana jest przydatna podczas ręcznego zatrzymywania lub pętli przerywania, ponieważ ParallelLoopResult przechowuje informacje, takie jak ostatnia iteracja, która została uruchomiona do ukończenia. Jeśli co najmniej jeden wyjątek wystąpi w jednym z wątków, System.AggregateException zostanie zgłoszony element .

W kodzie w tym przykładzie wartość zwracana For nie jest używana.

Analiza i wydajność

Możesz użyć Kreatora wydajności, aby wyświetlić użycie procesora CPU na komputerze. W ramach eksperymentu zwiększ liczbę kolumn i wierszy w macierzach. Im większe macierze, tym większa różnica wydajności między równoległymi i sekwencyjnymi wersjami obliczeń. Gdy macierz jest mała, wersja sekwencka będzie działać szybciej ze względu na obciążenie podczas konfigurowania pętli równoległej.

Synchroniczne wywołania zasobów udostępnionych, takich jak konsola lub system plików, znacznie obniży wydajność pętli równoległej. Podczas mierzenia wydajności spróbuj uniknąć wywołań, takich jak Console.WriteLine wewnątrz pętli.

Kompilowanie kodu

Skopiuj i wklej ten kod do projektu programu Visual Studio.

Zobacz też