Cómo: Escribir un bucle Parallel.For simple
En este ejemplo se muestra cómo utilizar la sobrecarga más simple del método Parallel.For para calcular el producto de dos matrices. También se muestra cómo utilizar la clase System.Diagnostics.Stopwatch para comparar el rendimiento de un bucle paralelo con un bucle no paralelo.
Nota |
---|
En esta documentación, se utilizan expresiones lambda para definir delegados en la TPL.Si no está familiarizado con las expresiones lambda en C# o Visual Basic, vea Expresiones lambda en PLINQ y TPL. |
Ejemplo
' 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
}
}
Puede utilizar la sobrecarga más básica del método For si no necesita cancelar ni interrumpir las iteraciones, ni mantener un estado local de subproceso.
Al paralelizar un código, incluidos los bucles, un objetivo importante consiste en hacer tanto uso de los procesadores como sea posible, sin excederse hasta el punto de que la sobrecarga del procesamiento en paralelo anule las ventajas en el rendimiento. En este ejemplo determinado, solamente se paraleliza el bucle exterior, ya que en el bucle interior no se realiza demasiado trabajo. La combinación de una cantidad pequeña de trabajo y los efectos no deseados en la memoria caché puede producir la degradación del rendimiento en los bucles paralelos anidados. Por consiguiente, paralelizar el bucle exterior solo es la mejor manera de maximizar las ventajas de simultaneidad en la mayoría de los sistemas.
Delegado
El tercer parámetro de esta sobrecarga de For es un delegado de tipo Action<int> en C# o Action(Of Integer) en Visual Basic. Un delegado Action siempre devuelve void, tanto si no tiene parámetros como si tiene uno o dieciséis. En Visual Basic, el comportamiento de Action se define con Sub. En el ejemplo se utiliza una expresión lambda para crear el delegado, pero también se puede crear de otras formas. Para obtener más información, vea Expresiones lambda en PLINQ y TPL.
Valor de iteración
El delegado toma un único parámetro de entrada cuyo valor es la iteración actual. El runtime proporciona este valor de iteración y su valor inicial es el índice del primer elemento del segmento (partición) del origen que se procesa en el subproceso actual.
Si requiere más control sobre el nivel de simultaneidad, utilice una de las sobrecargas que toma un parámetro de entrada System.Threading.Tasks.ParallelOptions, como: Parallel.For(Int32, Int32, ParallelOptions, Action<Int32, ParallelLoopState>).
Valor devuelto y control de excepciones
For devuelve un objeto System.Threading.Tasks.ParallelLoopResult cuando se han completado todos los subprocesos. Este valor devuelto es útil si se detiene o se interrumpe la iteración del bucle de forma manual, ya que ParallelLoopResult almacena información como la última iteración que se ejecutó hasta finalizar. Si se producen una o más excepciones en uno de los subprocesos, se inicia System.AggregateException.
En el código de este ejemplo, no se usa el valor devuelto de For.
Análisis y rendimiento
Puede utilizar el Asistente de rendimiento para ver el uso de la CPU en el equipo. Como experimento, aumente el número de columnas y filas en las matrices. Cuanto mayores son las matrices, mayor es la diferencia de rendimiento entre las versiones en paralelo y en serie del cálculo. Si la matriz es pequeña, la versión en serie se ejecutará más rápidamente debido a la sobrecarga de la configuración del bucle paralelo.
Las llamadas sincrónicas a los recursos compartidos, como la consola o el sistema de archivos, degradarán de forma significativa el rendimiento de un bucle paralelo. Al medir el rendimiento, intente evitar llamadas como Console.WriteLine dentro del bucle.
Compilar el código
- Corte y pegue este código en un proyecto de Visual Studio 2010.