다음을 통해 공유


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

이 예제에서는 Parallel.For 메서드의 가장 간단한 오버로드를 사용하여 두 매트릭스의 곱을 계산하는 방법을 보여 줍니다. 또한 System.Diagnostics.Stopwatch 클래스를 사용하여 병렬 루프와 비병렬 루프의 성능을 비교하는 방법도 보여 줍니다.

참고참고

이 문서에서는 람다 식을 사용하여 TPL에 대리자를 정의합니다.C# 또는 Visual Basic의 람다 식에 익숙하지 않으면 PLINQ 및 TPL의 람다 식을 참조하십시오.

예제

' How to: Write a Simple Parallel.For Loop 
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
                For k As Integer = 0 To matACols - 1
                    result(i, j) += matA(i, k) * matB(k, j)
                Next
            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
                                          ' Use a temporary to improve parallel performance.
                                          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.WriteLine("Executing sequential loop...")
        Dim stopwatch As New Stopwatch()
        stopwatch.Start()

        MultiplyMatricesSequential(m1, m2, result)
        stopwatch.[Stop]()
        Console.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.WriteLine("Executing parallel loop...")
        stopwatch.Start()
        MultiplyMatricesParallel(m1, m2, result)
        stopwatch.[Stop]()
        Console.WriteLine("Parallel loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds)
        OfferToPrint(rowCount, colCount2, result)

        ' Keep the console window open in debug mode.
        Console.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.WriteLine("Computation complete. Print results? y/n")
        Dim c As Char = Console.ReadKey().KeyChar
        If c = "y"c OrElse c = "Y"c 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
namespace MultiplyMatrices
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Concurrent;
    using System.Diagnostics;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        #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++)
                {
                    for (int k = 0; k < matACols; k++)
                    {
                        result[i, j] += matA[i, k] * matB[k, j];
                    }
                }
            }
        }
        #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++)
                {
                    // Use a temporary to improve parallel performance.
                    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.WriteLine("Executing sequential loop...");
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            MultiplyMatricesSequential(m1, m2, result);
            stopwatch.Stop();
            Console.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.WriteLine("Executing parallel loop...");
            stopwatch.Start();
            MultiplyMatricesParallel(m1, m2, result);
            stopwatch.Stop();
            Console.WriteLine("Parallel loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds);
            OfferToPrint(rowCount, colCount2, result);

            // Keep the console window open in debug mode.
            Console.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.WriteLine("Computation complete. Print results? y/n");
            char c = Console.ReadKey().KeyChar;
            if (c == 'y' || c == 'Y')
            {
                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
    }

}

반복을 취소 또는 중단하거나 스레드 로컬 상태를 유지할 필요가 없는 경우에는 For 메서드의 가장 기본적인 오버로드를 사용할 수 있습니다.

루프가 포함된 코드를 병렬화할 때 중요한 목표 중 하나는 병렬 처리를 위한 오버헤드로 인해 성능이 저하될 정도로 지나치게 병렬화하지 않으면서 프로세서를 가능한 한 최대로 활용하는 것입니다. 이 예제의 경우 내부 루프에서 수행되는 작업이 그다지 많지 않으므로 외부 루프만 병렬화합니다. 작업 양이 적은데 불필요하게 캐시의 영향을 받으면 중첩된 병렬 루프에서 성능이 저하될 수 있습니다. 따라서 대부분의 시스템에서 동시성의 이점을 최대화하려면 외부 루프만 병렬화하는 것이 가장 좋은 방법입니다.

대리자

For의 이 오버로드에 사용되는 세 번째 매개 변수는 Action<int> 형식(C#의 경우) 또는 Action(Of Integer) 형식(Visual Basic의 경우)의 대리자입니다. Action 대리자는 형식 매개 변수가 0개이든 1개이든 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 2010 프로젝트에 붙여넣습니다.

참고 항목

참조

For

ForEach

개념

데이터 병렬 처리(작업 병렬 라이브러리)

.NET Framework의 병렬 프로그래밍