Megosztás a következőn keresztül:


Hogyan készítsünk egyszerű párhuzamos For Loopot?

Ez a témakör két példát tartalmaz, amelyek a Parallel.For metódust szemléltetik. Az első a Parallel.For(Int64, Int64, Action<Int64>) metódus túlterhelését használja, a második pedig a Parallel.For(Int32, Int32, Action<Int32>) túlterhelést, a Parallel.For metódus két legegyszerűbb túlterhelését. A Parallel.For metódus két túlterhelését akkor használhatja, ha nincs szüksége a hurok megszakítására, a ciklus iterációinak megszakítására, vagy bármilyen szál-specifikus állapot fenntartására.

Megjegyzés

Ez a dokumentáció lambdakifejezéseket használ a meghatalmazottak definiálásához a TPL-ben. Ha nem ismeri a lambdakifejezéseket a C# vagy a Visual Basic alkalmazásban, tekintse meg Lambda-kifejezéseket a PLINQ-ban és a TPL-.

Az első példa egy könyvtárban lévő fájlok méretét számítja ki. A második két mátrix szorzatát számítja ki.

Példa könyvtárméretre

Ez a példa egy egyszerű parancssori segédprogram, amely kiszámítja egy könyvtárban lévő fájlok teljes méretét. Argumentumként egyetlen könyvtár elérési útját várja, és a címtárban lévő fájlok számát és teljes méretét jelenti. A címtár meglétének ellenőrzése után a Parallel.For metódussal számba veszi a címtárban lévő fájlokat, és meghatározza a fájlméretüket. Ezután minden fájlméret hozzáadódik a totalSize változóhoz. Vegye figyelembe, hogy az összeadás a Interlocked.Add meghívásával történik, hogy az összeadás atomi műveletként legyen végrehajtva. Ellenkező esetben több tevékenység is megpróbálhatja egyszerre frissíteni a totalSize változót.

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 '{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élda mátrixra és stopperóra

Ez a példa a Parallel.For metódust használja két mátrix szorzatának kiszámításához. Azt is bemutatja, hogyan használhatja a System.Diagnostics.Stopwatch osztályt egy párhuzamos hurok teljesítményének összehasonlítására egy nem párhuzamos hurokkal. Vegye figyelembe, hogy mivel nagy mennyiségű kimenetet hozhat létre, a példa lehetővé teszi a kimenet fájlba való átirányítását.

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 {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

Bármely kód– beleértve a hurkokat is – párhuzamosításakor fontos cél a processzorok lehető legnagyobb mértékű kihasználása anélkül, hogy túl párhuzamos lenne ahhoz a ponthoz, ahol a párhuzamos feldolgozás többletterhelése nem csökkenti a teljesítmény előnyeit. Ebben a konkrét példában csak a külső hurok párhuzamos, mert a belső hurokban nincs túl sok munka. A kis mennyiségű munka és a nemkívánatos gyorsítótár-hatások kombinációja teljesítménycsökkenést eredményezhet a beágyazott párhuzamos hurkokban. Ezért a külső hurok párhuzamosítása a legjobb módszer az egyidejűség előnyeinek maximalizálására a legtöbb rendszerben.

A meghatalmazott

A For túlterhelésének harmadik paramétere egy Action<int> típusú delegált a C#-ban vagy egy Action(Of Integer) típusú delegált a Visual Basic-ben. Egy Action delegált, függetlenül attól, hogy nulla, egy vagy tizenhat típusparamétert tartalmaz, mindig üres értéket ad vissza. A Visual Basicben egy Action viselkedését egy Subdefiniálja. A példa egy lambda kifejezéssel hozza létre a delegáltat, de más módokon is létrehozhatja a delegáltat. További információ: Lambda-kifejezések a PLINQ-ban és a TPL-.

Az iterációs érték

A meghatalmazott egyetlen bemeneti paramétert vesz fel, amelynek értéke az aktuális iteráció. Ezt az iterációs értéket a futtatókörnyezet adja meg, a kiindulási értéke pedig az aktuális szálon feldolgozott forrás szegmensének (partíciójának) első elemének indexe.

Ha az egyidejűségi szint nagyobb mértékű szabályozására van szüksége, használja az egyik olyan túlterhelést, amely egy System.Threading.Tasks.ParallelOptions bemeneti paramétert vesz igénybe, például: Parallel.For(Int32, Int32, ParallelOptions, Action<Int32,ParallelLoopState>).

Visszaadott érték és kivételkezelés

For visszaad egy System.Threading.Tasks.ParallelLoopResult objektumot, amikor az összes szál befejeződött. Ez a visszatérési érték akkor hasznos, ha manuálisan állítja le vagy szakítja meg a ciklus iterációt, mert a ParallelLoopResult olyan információkat tárol, mint például az utolsó iteráció, amely a befejezésig futott. Ha egy vagy több kivétel fordul elő az egyik szálon, egy System.AggregateException lesz feldobva.

A példában szereplő kódban a For visszatérési értéke nem használatos.

Elemzés és teljesítmény

A Teljesítmény varázslóval megtekintheti a számítógép processzorhasználatát. Kísérletként növelje a mátrixok oszlopainak és sorainak számát. Minél nagyobbak az mátrixok, annál nagyobb a teljesítménybeli különbség a számítás párhuzamos és szekvenciális verziói között. Ha a mátrix kicsi, a szekvenciális verzió gyorsabban fog futni, mivel a párhuzamos ciklus beállításakor nagy a terhelés.

A megosztott erőforrások, például a konzol vagy a fájlrendszer szinkron hívásai jelentősen rontják a párhuzamos hurkok teljesítményét. A teljesítmény mérése során próbálja meg elkerülni a Console.WriteLine-hoz hasonló hívásokat a hurokban.

A kód fordítása

Másolja és illessze be a kódot egy Visual Studio-projektbe.

Lásd még