Дана следующая простая задача поиска нечетных чисел в одномерном массиве:
begin
odds := 0;
Ticks := TThread.GetTickCount;
for i := 0 to MaxArr-1 do
if ArrXY[i] mod 2 = 0 then
Inc(odds);
Ticks := TThread.GetTickCount - Ticks;
writeln('Serial: ' + Ticks.ToString + 'ms, odds: ' + odds.ToString);
end;
Похоже, это хороший кандидат для параллельной обработки. Таким образом, может возникнуть соблазн использовать следующую версию TParallel.For:
begin
odds := 0;
Ticks := TThread.GetTickCount;
TParallel.For(0, MaxArr-1, procedure(I:Integer)
begin
if ArrXY[i] mod 2 = 0 then
inc(odds);
end);
Ticks := TThread.GetTickCount - Ticks;
writeln('Parallel - false odds: ' + Ticks.ToString + 'ms, odds: ' + odds.ToString);
end;
Результат этого параллельного вычисления несколько удивителен в двух отношениях:
Количество подсчитанных коэффициентов неверно
Время исполнения больше, чем в серийной версии
1) Это объяснимо, потому что мы не защищали переменную шансов для одновременного доступа. Поэтому, чтобы исправить это, мы должны вместо этого использовать TInterlocked.Increment(odds);
.
2) Также объяснимо: он демонстрирует эффекты ложного обмена.
В идеале решением проблемы ложного совместного использования было бы использование локальной переменной для хранения промежуточных результатов и суммирования этих посредников только в конце всех параллельных задач. И вот мой настоящий вопрос, который я не могу понять: есть ли способ добавить локальную переменную в мой анонимный метод? Обратите внимание, что простое объявление локальной переменной в теле анонимного метода не сработает, поскольку тело анонимного метода вызывается для каждой итерации. И если это каким-то образом выполнимо, есть ли способ получить мой промежуточный результат в конце каждой итерации задачи из анонимного метода?
Изменить: на самом деле меня не очень интересует подсчет шансов или эванса. Я использую это только для демонстрации эффекта.
И для полноты картины вот консольное приложение, демонстрирующее эффекты:
program Project4;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Threading, System.Classes, System.SyncObjs;
const
MaxArr = 100000000;
var
Ticks: Cardinal;
i: Integer;
odds: Integer;
ArrXY: array of Integer;
procedure FillArray;
var
i: Integer;
j: Integer;
begin
SetLength(ArrXY, MaxArr);
for i := 0 to MaxArr-1 do
ArrXY[i]:=Random(MaxInt);
end;
procedure Parallel;
begin
odds := 0;
Ticks := TThread.GetTickCount;
TParallel.For(0, MaxArr-1, procedure(I:Integer)
begin
if ArrXY[i] mod 2 = 0 then
TInterlocked.Increment(odds);
end);
Ticks := TThread.GetTickCount - Ticks;
writeln('Parallel: ' + Ticks.ToString + 'ms, odds: ' + odds.ToString);
end;
procedure ParallelFalseResult;
begin
odds := 0;
Ticks := TThread.GetTickCount;
TParallel.For(0, MaxArr-1, procedure(I:Integer)
begin
if ArrXY[i] mod 2 = 0 then
inc(odds);
end);
Ticks := TThread.GetTickCount - Ticks;
writeln('Parallel - false odds: ' + Ticks.ToString + 'ms, odds: ' + odds.ToString);
end;
procedure Serial;
begin
odds := 0;
Ticks := TThread.GetTickCount;
for i := 0 to MaxArr-1 do
if ArrXY[i] mod 2 = 0 then
Inc(odds);
Ticks := TThread.GetTickCount - Ticks;
writeln('Serial: ' + Ticks.ToString + 'ms, odds: ' + odds.ToString);
end;
begin
try
FillArray;
Serial;
ParallelFalseResult;
Parallel;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.