다음을 통해 공유


방법: 간단한 Parallel.For 루프 작성하기

이 항목에는 Parallel.For 메서드를 보여 주는 두 가지 예제가 포함되어 있습니다. 첫 번째는 Parallel.For(Int64, Int64, Action<Int64>) 메서드 오버로드를 사용하고, 두 번째 오버로드는 Parallel.For 메서드의 가장 간단한 두 오버로드인 Parallel.For(Int32, Int32, Action<Int32>) 오버로드를 사용합니다. 루프를 취소하거나 루프 반복을 중단하거나 스레드 로컬 상태를 유지할 필요가 없는 경우 Parallel.For 메서드의 이러한 두 오버로드를 사용할 수 있습니다.

비고

이 설명서는 람다 식을 사용하여 TPL에서 대리자를 정의합니다. C# 또는 Visual Basic의 람다 식에 익숙하지 않은 경우 PLINQ 및 TPL 람다 식을 참조하세요.

첫 번째 예제에서는 단일 디렉터리의 파일 크기를 계산합니다. 두 번째 행렬은 두 행렬의 곱을 계산합니다.

디렉터리 크기 예제

이 예제는 디렉터리에 있는 파일의 총 크기를 계산하는 간단한 명령줄 유틸리티입니다. 단일 디렉터리 경로를 인수로 예상하며 해당 디렉터리에 있는 파일의 수와 총 크기를 보고합니다. 디렉터리가 있는지 확인한 후 Parallel.For 메서드를 사용하여 디렉터리의 파일을 열거하고 파일 크기를 확인합니다. 그런 다음 각 파일 크기가 totalSize 변수에 추가됩니다. 추가는 추가가 원자성 연산으로 수행되도록 Interlocked.Add 호출하여 수행됩니다. 그렇지 않으면 여러 태스크가 totalSize 변수를 동시에 업데이트하려고 할 수 있습니다.

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

행렬 및 스톱워치 예제

이 예제에서는 Parallel.For 메서드를 사용하여 두 행렬의 곱을 계산합니다. 또한 System.Diagnostics.Stopwatch 클래스를 사용하여 병렬 루프의 성능을 비 병렬 루프와 비교하는 방법도 보여 줍니다. 대량의 출력을 생성할 수 있으므로 이 예제에서는 출력을 파일로 리디렉션할 수 있습니다.

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

루프를 비롯한 모든 코드를 병렬 처리할 때 한 가지 중요한 목표는 병렬 처리를 위한 오버헤드가 성능 이점을 부정하는 지점까지 병렬화하지 않고 프로세서를 최대한 활용하는 것입니다. 이 특정 예제에서는 내부 루프에서 수행되는 작업이 별로 없기 때문에 외부 루프만 병렬 처리됩니다. 적은 양의 작업과 바람직하지 않은 캐시 효과를 조합하면 중첩된 병렬 루프에서 성능이 저하될 수 있습니다. 따라서 외부 루프만 병렬화하는 것이 대부분의 시스템에서 동시성의 이점을 최대화하는 가장 좋은 방법입니다.

대리자

For 오버로드의 세 번째 매개 변수는 C#의 Action<int> 형식 또는 Visual Basic의 Action(Of Integer) 형식의 대리자입니다. Action 대리자는 0개, 하나 또는 16개의 형식 매개 변수가 있는지 여부에 관계없이 항상 void를 반환합니다. Visual Basic에서 Action 동작은 Sub사용하여 정의됩니다. 이 예제에서는 람다 식을 사용하여 대리자를 만들지만 다른 방법으로도 대리자를 만들 수 있습니다. 자세한 내용은 PLINQ 및 TPL 람다 식을 참조하세요.

반복 값

대리자는 값이 현재 반복인 단일 입력 매개 변수를 사용합니다. 이 반복 값은 런타임에서 제공되고 시작 값은 현재 스레드에서 처리되는 원본의 세그먼트(파티션)에 있는 첫 번째 요소의 인덱스입니다.

동시성 수준에 대한 더 많은 제어가 필요한 경우 Parallel.For(Int32, Int32, ParallelOptions, Action<Int32,ParallelLoopState>)같은 System.Threading.Tasks.ParallelOptions 입력 매개 변수를 사용하는 오버로드 중 하나를 사용합니다.

반환 값 및 예외 처리

For 반환은 모든 스레드가 완료되면 System.Threading.Tasks.ParallelLoopResult 개체를 사용합니다. 이 반환 값은 루프 반복을 수동으로 중지하거나 중단하는 경우에 유용합니다. ParallelLoopResult 완료까지 실행된 마지막 반복과 같은 정보를 저장하기 때문입니다. 스레드 중 하나에서 하나 이상의 예외가 발생하면 System.AggregateException throw됩니다.

이 예제의 코드에서는 For 반환 값이 사용되지 않습니다.

분석 및 성능

성능 마법사를 사용하여 컴퓨터의 CPU 사용량을 볼 수 있습니다. 실험적으로 행렬의 열과 행 수를 늘립니다. 행렬이 클수록 계산의 병렬 버전과 순차 버전 간의 성능 차이가 커지게 됩니다. 행렬이 작으면 병렬 루프를 설정하는 오버헤드로 인해 순차 버전이 더 빠르게 실행됩니다.

콘솔 또는 파일 시스템과 같은 공유 리소스에 대한 동기 호출은 병렬 루프의 성능을 크게 저하합니다. 성능을 측정할 때 루프 내에서 Console.WriteLine 같은 호출을 피합니다.

코드 컴파일

이 코드를 복사하여 Visual Studio 프로젝트에 붙여넣습니다.

참고하십시오