Como: Criar um Loop de Parallel. for simples
Este exemplo mostra como usar a sobrecarga mais simples do Parallel.For método para calcular o produto de duas matrizes. Ele também mostra como usar o System.Diagnostics.Stopwatch classe para comparar o desempenho de um loop em paralelo com um loop sem paralelo.
Observação
Esta documentação usa expressões lambda para definir os delegados na TPL.Se você não estiver familiarizado com as expressões lambda em C# ou Visual Basic, consulte Expressões lambda no PLINQ e TPL.
Exemplo
' 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
}
}
Você pode usar a sobrecarga mais básica da For método quando não precisar cancelar ou dividir as iterações ou manter qualquer estado de segmento local.
Quando a paralelização de qualquer código, incluindo loops, um objetivo importante é utilizar os processadores tanto quanto possíveis sem sobre paralelizar até o ponto onde a sobrecarga de processamento paralelo anula os benefícios de desempenho. Nesse exemplo específico, somente o loop externo é paralelizado porque não há muito trabalho realizado no loop interno. A combinação de uma pequena quantidade de trabalho e os efeitos indesejáveis do cache pode resultar em degradação do desempenho de nested loops paralelos. Portanto, a paralelização de loop externo é apenas a melhor forma de maximizar os benefícios de simultaneidade na maioria dos sistemas.
O delegado
O terceiro parâmetro dessa sobrecarga do For é um delegado do tipo Action<int> em C# ou Action(Of Integer) Visual Basic. Um Action delegar, quer ele tenha um zero ou dezesseis parâmetros de tipo, sempre retorna void. No Visual Basic, o comportamento de um Action é definido com um Sub. O exemplo usa uma expressão lambda para criar o delegado, mas você pode criar o delegado de outras maneiras. Para obter mais informações, consulte Expressões lambda no PLINQ e TPL.
O valor de iteração
O delegado usa um único parâmetro de entrada, cujo valor é a iteração atual. Esse valor de iteração é fornecido pelo tempo de execução e seu valor inicial é o índice do primeiro elemento no segmento (partição) da fonte que está sendo processado no thread atual.
Se precisar de mais controle sobre o nível de simultaneidade, use uma das sobrecargas que leva um System.Threading.Tasks.ParallelOptions como parâmetro de entrada: Parallel.For(Int32, Int32, ParallelOptions, Action<Int32, ParallelLoopState>).
Valor de retorno e tratamento de exceção
ForRetorna um System.Threading.Tasks.ParallelLoopResult quando tem concluído a todos os segmentos de objeto. Isso retornará o valor é útil quando você está interrompendo ou quebra iteração de loop manualmente, porque o ParallelLoopResult armazena informações como a última iteração que executava a conclusão. Se uma ou mais exceções ocorrem em um dos threads, um System.AggregateException será lançada.
No código neste exemplo, o valor de retorno de For não é usado.
Análise e desempenho
Você pode usar o Assistente de desempenho para exibir a utilização da CPU no seu computador. Como exercício, aumente o número de colunas e linhas de matrizes. Quanto maiores as matrizes, quanto maior a diferença de desempenho entre as versões em paralelas e seqüenciais da computação. Quando a matriz é pequena, a versão seqüencial será executado mais rapidamente a causa da sobrecarga em Configurando o loop paralelo.
As chamadas síncronas a recursos compartilhados, como o Console ou o sistema de arquivos, significativamente degrada o desempenho de um loop em paralelo. Ao medir o desempenho, tente evitar chamadas, como Console.WriteLine dentro do loop.
Compilando o código
- Recorte e cole este código em um projeto de 2010 de Visual Studio.