Given the following simple task of finding odd numbers in a one dimensional array:
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;
It looks like this would be a good candidate for parallel processing. So one might be tempted to use the following TParallel.For version:
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;
The result of this parallel computation is somewhat surprising in two respects:
The number of counted odds is wrong
The execution time is longer than in the serial version
1) Is explainable, because we did not protect the odds variable for concurrent access. So in order to fix this, we should use TInterlocked.Increment(odds);
instead.
2) Is also explainable: It exhibits the effects of false sharing.
Ideally the solution to the false sharing problem would be to use a local variable to store intermediate results and only at the end of all parallel tasks sum up those intermediaries. And here is my real question that I cannot get my head around: Is there any way to get a local variable into my anonymous method? Note, that simply declaring a local variable within the anonymous method body would not work, as the anonymous method body is called for each iteration. And if that is somehow doable, would there be a way to get my intermediate result at the end of each task iteration out of the anonymous method?
Edit: I am actually not really interested in counting odds, or evans. I only use this to demonstrate the effect.
And for completeness reasons here is a console app demonstrating the effects:
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.