Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом разделе содержатся два примера, которые иллюстрируют метод 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 '{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 {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
, независимо от того, имеет ли он ноль, один или шестнадцать параметров типа, всегда возвращает 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 не используется.
Анализ и производительность
Мастер производительности можно использовать для просмотра использования ЦП на компьютере. В качестве эксперимента увеличьте количество столбцов и строк в матрицах. Чем больше матрицы, тем больше разница в производительности между параллельными и последовательными версиями вычислений. Если матрица небольшая, последовательная версия будет работать быстрее из-за накладных расходов при настройке параллельного цикла.
Синхронные вызовы общих ресурсов, таких как консоль или файловая система, значительно унизят производительность параллельного цикла. При измерении производительности старайтесь избегать вызовов типа Console.WriteLine в цикле.
Компиляция кода
Скопируйте и вставьте этот код в проект Visual Studio.