Share via


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

이 항목에는 Parallel.For 메서드를 설명하는 두 가지 예제가 포함되어 있습니다. 첫 번째 예제에서는 Parallel.For(Int64, Int64, Action<Int64>) 메서드 오버로드를 사용하고, 두 번째 예제에서는 Parallel.For(Int32, Int32, Action<Int32>) 오버로드를 사용합니다. Parallel.For 메서드의 가장 간단한 오버로드 중 두 개입니다. 루프를 취소하거나, 루프 반복을 중단하거나, 스레드 로컬 상태를 유지할 필요가 없는 경우 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 '{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

행렬 및 스톱워치 예제

이 예제에서는 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 {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

루프를 포함하여 코드를 병렬 처리하는 경우 한 가지 중요한 목표는 병렬 처리의 오버헤드가 성능 혜택보다 큰 지점까지 과도하게 병렬 처리하지 않고 최대한 많은 프로세서를 활용하는 것입니다. 이 특정 예제에서는 내부 루프에서 많은 작업이 수행되지 않으므로 외부 루프만 병렬 처리됩니다. 적은 작업량과 원치 않는 캐시 결과로 인해 중첩된 병렬 루프에서 성능이 저하될 수 있습니다. 따라서 외부 루프만 병렬 처리하는 것이 대부분의 시스템에서 동시성의 이점을 극대화하는 가장 좋은 방법입니다.

대리자

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

반복 값

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

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

반환 값 및 예외 처리

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

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

분석 및 성능

성능 마법사를 사용하여 컴퓨터의 CPU 사용량을 볼 수 있습니다. 실험적으로 행렬의 열과 행 수를 늘려봅니다. 행렬이 클수록 병렬 및 순차적 계산 버전 간의 성능 차이가 커집니다. 행렬이 작으면 병렬 루프를 설정하는 오버헤드 때문에 순차적 버전이 더 빨리 실행됩니다.

콘솔 또는 파일 시스템과 같은 공유 리소스에 대한 동기 호출은 병렬 루프의 성능을 상당히 저하시킵니다. 성능을 측정하는 경우 루프 내에서 Console.WriteLine과 같은 호출을 사용하지 않도록 하세요.

코드 컴파일

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

참고 항목