Gör så här: Skriva en enkel parallell.For-loop
Det här avsnittet innehåller två exempel som illustrerar Parallel.For metoden. Den första använder Parallel.For(Int64, Int64, Action<Int64>) metodens överlagring, och den andra använder Parallel.For(Int32, Int32, Action<Int32>) överlagringen, de två enklaste överlagringarna av Parallel.For metoden. Du kan använda dessa två överlagringar av Parallel.For metoden när du inte behöver avbryta loopen, bryta ut från loop-iterationerna eller behålla något trådlokalt tillstånd.
Kommentar
Den här dokumentationen använder lambda-uttryck för att definiera ombud i TPL. Om du inte är bekant med lambda-uttryck i C# eller Visual Basic kan du läsa Lambda-uttryck i PLINQ och TPL.
I det första exemplet beräknas storleken på filer i en enda katalog. Den andra beräknar produkten av två matriser.
Exempel på katalogstorlek
Det här exemplet är ett enkelt kommandoradsverktyg som beräknar den totala storleken på filer i en katalog. Den förväntar sig en enskild katalogsökväg som ett argument och rapporterar antalet och den totala storleken på filerna i katalogen. När du har verifierat att katalogen finns använder den Parallel.For metoden för att räkna upp filerna i katalogen och fastställa deras filstorlekar. Varje filstorlek läggs sedan till i variabeln totalSize
. Observera att tillägget utförs genom att anropa Interlocked.Add så att tillägget utförs som en atomisk åtgärd. Annars kan flera aktiviteter försöka uppdatera variabeln totalSize
samtidigt.
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
Exempel på matris och stoppur
I det Parallel.For här exemplet används metoden för att beräkna produkten av två matriser. Den visar också hur du använder System.Diagnostics.Stopwatch klassen för att jämföra prestanda för en parallell loop med en icke-parallell loop. Observera att eftersom det kan generera en stor volym utdata tillåter exemplet att utdata omdirigeras till en fil.
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
När du parallelliserar någon kod, inklusive loopar, är ett viktigt mål att använda processorerna så mycket som möjligt utan att parallellisera till den punkt där omkostnaderna för parallell bearbetning negerar eventuella prestandafördelar. I det här exemplet parallelliseras endast den yttre loopen eftersom det inte utförs särskilt mycket arbete i den inre loopen. Kombinationen av en liten mängd arbete och oönskade cacheeffekter kan leda till prestandaförsämring i kapslade parallella loopar. Därför är parallellisering av den yttre loopen det bästa sättet att maximera fördelarna med samtidighet på de flesta system.
Ombudet
Den tredje parametern för den här överbelastningen For är ett ombud av typen Action<int>
i C# eller Action(Of Integer)
Visual Basic. Ett Action
ombud, oavsett om det har noll, en eller sexton typparametrar, returnerar alltid void. I Visual Basic definieras beteendet för en Action
med en Sub
. I exemplet används ett lambda-uttryck för att skapa ombudet, men du kan även skapa ombudet på andra sätt. Mer information finns i Lambda-uttryck i PLINQ och TPL.
Iterationsvärdet
Ombudet tar en enskild indataparameter vars värde är den aktuella iterationen. Det här iterationsvärdet tillhandahålls av körningen och dess startvärde är indexet för det första elementet i segmentet (partitionen) för den källa som bearbetas i den aktuella tråden.
Om du behöver mer kontroll över samtidighetsnivån använder du en av de överlagringar som tar en System.Threading.Tasks.ParallelOptions indataparameter, till exempel: Parallel.For(Int32, Int32, ParallelOptions, Action<Int32,ParallelLoopState>).
Returvärde och undantagshantering
For returnerar använder ett System.Threading.Tasks.ParallelLoopResult objekt när alla trådar har slutförts. Det här returvärdet är användbart när du stoppar eller bryter loop-iterationen manuellt, eftersom ParallelLoopResult lagrar information, till exempel den senaste iterationen som kördes till slutförande. Om ett eller flera undantag inträffar i en av trådarna genereras ett System.AggregateException .
I koden i det här exemplet används inte returvärdet For för.
Analys och prestanda
Du kan använda prestandaguiden för att visa CPU-användning på datorn. Som ett experiment ökar du antalet kolumner och rader i matriserna. Ju större matriser desto större är prestandaskillnaden mellan de parallella och sekventiella versionerna av beräkningen. När matrisen är liten körs den sekventiella versionen snabbare på grund av omkostnaderna för att konfigurera den parallella loopen.
Synkrona anrop till delade resurser, till exempel konsolen eller filsystemet, försämrar avsevärt prestandan för en parallell loop. Försök att undvika anrop som Console.WriteLine i loopen när du mäter prestanda.
Kompilera koden
Kopiera och klistra in den här koden i ett Visual Studio-projekt.