Share via


Postupy: Zápis jednoduché smyčky Parallel.For

Toto téma obsahuje dva příklady, které ilustrují metodu Parallel.For . První používá Parallel.For(Int64, Int64, Action<Int64>) přetížení metody a druhá používá Parallel.For(Int32, Int32, Action<Int32>) přetížení, dvě nejjednodušší přetížení Parallel.For metody. Tyto dvě přetížení Parallel.For metody můžete použít, když nemusíte rušit smyčku, přerušovat iterace smyčky nebo udržovat jakýkoli místní stav vlákna.

Poznámka:

Tato dokumentace používá k definování delegátů v TPL lambda výrazy. Pokud výrazy lambda v jazyce C# nebo Visual Basic neznáte, přečtěte si téma Výrazy lambda v PLINQ a TPL.

První příklad vypočítá velikost souborů v jednom adresáři. Druhý vypočítá součin dvou matic.

Příklad velikosti adresáře

Tento příklad je jednoduchý nástroj příkazového řádku, který vypočítá celkovou velikost souborů v adresáři. Očekává jako argument jednu cestu k adresáři a hlásí počet a celkovou velikost souborů v tomto adresáři. Po ověření, že adresář existuje, použije metodu Parallel.For k vytvoření výčtu souborů v adresáři a určení jejich velikostí souborů. Každá velikost souboru se pak přidá do totalSize proměnné. Všimněte si, že sčítání se provádí voláním Interlocked.Add tak, aby se sčítání provádělo jako atomická operace. Jinak by se několik úkolů mohlo pokusit aktualizovat proměnnou totalSize současně.

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

Příklad matic a stopek

Tento příklad používá metodu Parallel.For k výpočtu součinu dvou matic. Ukazuje také, jak pomocí System.Diagnostics.Stopwatch třídy porovnat výkon paralelní smyčky s ne parallel smyčkou. Všimněte si, že protože může generovat velký objem výstupu, příklad umožňuje přesměrování výstupu do souboru.

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

Při paralelizaci libovolného kódu, včetně smyček, je jedním z důležitých cílů co nejvíce využití procesorů bez nutnosti paralelizace do bodu, kdy režie paralelního zpracování neguje všechny výhody výkonu. V tomto konkrétním příkladu je paralelizována pouze vnější smyčka, protože ve vnitřní smyčce není příliš mnoho práce. Kombinace malého množství práce a nežádoucích účinků mezipaměti může vést ke snížení výkonu ve vnořených paralelních smyček. Proto paralelizace vnější smyčky je nejlepší způsob, jak maximalizovat výhody souběžnosti ve většině systémů.

Delegát

Třetí parametr tohoto přetížení For je delegát typu Action<int> v jazyce C# nebo Action(Of Integer) v jazyce Visual Basic. Delegát Action , ať už má nulový, jeden nebo šestnáct parametrů typu, vždy vrátí hodnotu void. V jazyce Visual Basic je chování definováno Action pomocí .Sub V příkladu se k vytvoření delegáta používá výraz lambda, ale delegáta můžete vytvořit i jinými způsoby. Další informace naleznete v tématu Výrazy lambda v PLINQ a TPL.

Hodnota iterace

Delegát přebírá jeden vstupní parametr, jehož hodnotou je aktuální iterace. Tato hodnota iterace je poskytována modulem runtime a jeho počáteční hodnotou je index prvního prvku v segmentu (oddílu) zdroje, který se zpracovává v aktuálním vlákně.

Pokud potřebujete větší kontrolu nad úrovní souběžnosti, použijte jedno z přetížení, které přebírá System.Threading.Tasks.ParallelOptions vstupní parametr, například: Parallel.For(Int32, Int32, ParallelOptions, Action<Int32,ParallelLoopState>).

Vrácení hodnoty a zpracování výjimek

For vrátí objekt, System.Threading.Tasks.ParallelLoopResult pokud jsou dokončena všechna vlákna. Tato návratová hodnota je užitečná, když ručně zastavíte nebo přerušíte iteraci smyčky, protože ParallelLoopResult ukládá informace, jako je poslední iterace, která byla spuštěna na dokončení. Pokud na jednom z vláken dojde k jedné nebo více výjimkám, System.AggregateException vyvolá se chyba.

V kódu v tomto příkladu se návratová For hodnota nepoužívá.

Analýza a výkon

Pomocí Průvodce výkonem můžete zobrazit využití procesoru na vašem počítači. Při experimentu zvyšte počet sloupců a řádků v maticích. Čím větší jsou matice, tím větší je rozdíl výkonu mezi paralelními a sekvenčními verzemi výpočtu. Pokud je matice malá, sekvenční verze se spustí rychleji kvůli režii při nastavování paralelní smyčky.

Synchronní volání sdílených prostředků, jako je konzola nebo systém souborů, výrazně sníží výkon paralelní smyčky. Při měření výkonu se pokuste vyhnout voláním, jako Console.WriteLine je například smyčka.

Kompilace kódu

Zkopírujte a vložte tento kód do projektu sady Visual Studio.

Viz také