Bagikan melalui


Cara Menulis Loop Parallel.For Sederhana

Topik ini berisi dua contoh yang mengilustrasikan metode Parallel.For. Yang pertama menggunakan Parallel.For(Int64, Int64, Action<Int64>) metode kelebihan beban, dan yang kedua menggunakan Parallel.For(Int32, Int32, Action<Int32>) kelebihan beban, dua kelebihan Parallel.For metode yang paling sederhana. Anda dapat menggunakan kedua overload metode Parallel.For ini ketika Anda tidak perlu membatalkan perulangan, keluar dari iterasi perulangan, atau mempertahankan status thread-local apa pun.

Nota

Dokumentasi ini menggunakan ekspresi lambda untuk menentukan delegasi dalam TPL. Jika Anda tidak terbiasa dengan ekspresi lambda di C# atau Visual Basic, lihat Ekspresi Lambda di PLINQ dan TPL.

Contoh pertama menghitung ukuran file dalam satu direktori. Yang kedua menghitung produk dari dua matriks.

Contoh ukuran direktori

Contoh ini adalah utilitas baris perintah sederhana yang menghitung ukuran total file dalam direktori. Ini mengharapkan jalur direktori tunggal sebagai argumen, dan melaporkan jumlah dan ukuran total file dalam direktori tersebut. Setelah memverifikasi bahwa direktori ada, direktori menggunakan Parallel.For metode untuk menghitung file di direktori dan menentukan ukuran filenya. Setiap ukuran file kemudian ditambahkan ke totalSize variabel . Perhatikan bahwa penambahan dilakukan dengan memanggil Interlocked.Add sehingga penambahan dilakukan sebagai operasi atomik. Jika tidak, beberapa tugas dapat mencoba memperbarui totalSize variabel secara bersamaan.

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

Contoh matriks dan stopwatch

Contoh ini menggunakan Parallel.For metode untuk menghitung produk dari dua matriks. Ini juga menunjukkan cara menggunakan System.Diagnostics.Stopwatch kelas untuk membandingkan performa perulangan paralel dengan perulangan non-paralel. Perhatikan bahwa, karena dapat menghasilkan output dalam volume besar, contohnya memungkinkan output untuk dialihkan ke file.

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

Ketika melakukan paralelisasi terhadap kode apa pun, termasuk perulangan, salah satu tujuan penting adalah memanfaatkan prosesor semaksimal mungkin tanpa melakukan paralelisasi berlebihan hingga overhead untuk pemrosesan paralel menghilangkan manfaat kinerja yang ada. Dalam contoh khusus ini, hanya perulangan luar yang diparalelkan karena tidak ada banyak pekerjaan yang dilakukan dalam perulangan bagian dalam. Kombinasi sejumlah kecil pekerjaan dan efek cache yang tidak diinginkan dapat mengakibatkan penurunan performa dalam perulangan paralel berlapis. Oleh karena itu, paralelisasi perulangan luar saja adalah cara terbaik untuk memaksimalkan manfaat konkurensi pada sebagian besar sistem.

Perwakilan

Parameter ketiga dari kelebihan beban For ini adalah delegasi jenis Action<int> dalam C# atau Action(Of Integer) di Visual Basic. Delegasi Action , apakah memiliki parameter jenis nol, satu atau enam belas, selalu mengembalikan kekosongan. Dalam Visual Basic, perilaku sebuah Action ditentukan dengan Sub. Contohnya menggunakan ekspresi lambda untuk membuat delegasi, tetapi Anda juga dapat membuat delegasi dengan cara lain. Untuk informasi selengkapnya, lihat Ekspresi Lambda di PLINQ dan TPL.

Nilai Iterasi

Delegasi mengambil parameter input tunggal yang nilainya adalah iterasi saat ini. Nilai iterasi ini disediakan oleh runtime dan nilai awalnya adalah indeks elemen pertama pada segmen (partisi) sumber yang sedang diproses pada utas saat ini.

Jika Anda memerlukan kontrol lebih besar atas tingkat konkurensi, gunakan salah satu overload yang memiliki parameter input System.Threading.Tasks.ParallelOptions, seperti: Parallel.For(Int32, Int32, ParallelOptions, Action<Int32,ParallelLoopState>).

Mengembalikan Nilai dan Penanganan Pengecualian

For mengembalikan menggunakan System.Threading.Tasks.ParallelLoopResult objek ketika semua utas telah selesai. Nilai pengembalian ini berguna ketika Anda menghentikan atau melanggar iterasi perulangan secara manual, karena ParallelLoopResult menyimpan informasi seperti iterasi terakhir yang berjalan hingga selesai. Jika satu atau beberapa pengecualian terjadi pada salah satu utas, maka System.AggregateException akan dipicu.

Pada kode di contoh ini, nilai pengembalian For tidak digunakan.

Analisis dan Performa

Anda bisa menggunakan Panduan Performa untuk melihat penggunaan CPU di komputer Anda. Sebagai eksperimen, tingkatkan jumlah kolom dan baris dalam matriks. Semakin besar matriks, semakin besar perbedaan performa antara versi komputasi paralel dan berurutan. Ketika matriks berukuran kecil, versi berurutan akan berjalan lebih cepat karena adanya overhead dalam menyiapkan perulangan paralel.

Panggilan sinkron ke sumber daya bersama, seperti Konsol atau Sistem File, akan secara signifikan menurunkan performa perulangan paralel. Saat mengukur performa, cobalah untuk menghindari panggilan seperti Console.WriteLine dalam perulangan.

Mengkompilasi Kode

Salin dan tempel kode ini ke dalam proyek Visual Studio.

Lihat juga