Delphi 2007 AsyncMultiSync Does not work - multithreading

I have tried to write a parallel threading example using AsyncCalls and the following is to parallel compute the number of prime numbers
program Project3;
{$APPTYPE CONSOLE}
uses
SysUtils,
Math,
Windows,
AsyncCalls in 'AsyncCalls.pas';
const
N = 1000000;
MAXT = 100;
var
threads : array of IAsyncCall;
ans: array of integer;
cnt: DWORD;
i, v, j, k, portion: integer;
function IsPrime(x: integer): boolean;
var
i: integer;
begin
if (x <= 1) then
begin
Result := False;
Exit;
end;
if (x = 2) then
begin
Result := True;
Exit;
end;
for i:= 2 to Ceil(Sqrt(x))do
begin
if (x mod i = 0) then
begin
Result := False;
Exit;
end;
end;
Result := True;
end;
procedure DoWork(left, right: integer; value: PInteger); cdecl;
var
i, cnt: integer;
begin
cnt := 0;
for i := left to right do
begin
if (IsPrime(i)) then
begin
Inc(cnt);
end;
end;
value^ := cnt;
end;
begin
// Paralell
cnt := GetTickCount;
SetLength(ans, MAXT);
SetLength(threads, MAXT);
portion := N div MAXT;
for i := 0 to MAXT - 2 do
begin
// left index
j := i * portion;
// right index
k := (i + 1) * portion - 1;
threads[i] := AsyncCall(#DoWork, [j, k, #ans[i]]);
end;
// last thread
j := (MAXT - 1) * portion;
threads[MAXT - 1] := AsyncCall(#DoWork, [j, N - 1, #ans[MAXT - 1]]);
// Join, doesn't seem to wait all
AsyncMultiSync(threads, True, INFINITE);
// ****Adding a delay to wait for all threads*****
// Sleep(1000);
// Sum the answer
v := 0;
for i := 0 to MAXT - 1 do
begin
Inc(v, ans[i]);
end;
Writeln('Parallel = ', GetTickCount - cnt);
Writeln('Answer = ', v);
// Serial
cnt := GetTickCount;
DoWork(0, N - 1, #v);
Writeln('Serial = ', GetTickCount - cnt);
Writeln('Answer = ', v);
Readln;
end.
Strange behaviour. The AsyncMultiSync does not seem waiting at all. If I do not add Sleep(1000), the output is
Parallel = 172
Answer = 0
Serial = 453
Answer = 78498
If I add Sleep(1000), the output is correct:
Parallel = 1188
Answer = 78498
Serial = 265
Answer = 78498
I tried using threads[i].Sync, and it produces similar results.
Did I miss something here?
The environment is D2007, Windows 10 64-bit Home

Documentation says not to exceed the maximum number of wait objects, 61.
So you will have to reduce the MaxT constant.

Related

How to modify AnsiStrPos function to return integer offset? (Delphi 7)

I am using Delphi 7 and I want function similar to ansipos, but it should use input offset argument and return integer offset. I have found code similar to this here https://github.com/Fr0sT-Brutal/Delphi_MiniRTL/blob/master/SysUtils.pas and I want to modify the output to be integer offset. How can I get the integer offset from PChar?
function AnsiStrPos(Str, SubStr: PChar): PChar;
var
L1, L2: Cardinal;
ByteType : TMbcsByteType;
begin
Result := nil;
if (Str = nil) or (Str^ = #0) or (SubStr = nil) or (SubStr^ = #0) then Exit;
L1 := StrLen(Str);
L2 := StrLen(SubStr);
Result := StrPos(Str, SubStr);
while (Result <> nil) and ((L1 - Cardinal(Result - Str)) >= L2) do
begin
ByteType := StrByteType(Str, Integer(Result-Str));
if (ByteType <> mbTrailByte) and
(AnsiCompareStr(Result, SubStr) = 0) then Exit;
if (ByteType = mbLeadByte) then Inc(Result);
Inc(Result);
Result := StrPos(Result, SubStr);
end;
Result := nil;
end;
For example str := "sub_string"; From "sub_string" I want find "string", start to search from position 4 and the result should be 0.
Try this solution:
function AnsiStrOffset(AStr, ASubStr: PChar; const AStartPos: Cardinal = 0): Integer;
var
VPos: PChar;
begin
Result := -1; // not found
if StrLen(AStr) <= AStartPos then begin
Exit;
end;
Inc(AStr, AStartPos);
VPos := AnsiStrPos(AStr, ASubStr);
if VPos <> nil then begin
Result := Int64(VPos) - Int64(AStr);
// add AStartPos to Result if you need offset from the string beginning
end;
end;
usage:
var
VOffs: Integer;
VStr, VSubStr: string;
begin
VStr := 'sub_string';
VSubStr := 'string';
VOffs := AnsiStrOffset(PChar(VStr), PChar(VSubStr), 4);
WriteLn(VOffs); // prints 0
end;

Delphi (10.2): fast Integer conversion to string with separator

Let's say we have this Integer 1234567890, we want it converted to a string with a separator = 1.234.567.890, we could do Format('%n',[1234567890.0]); but it's very slow. I wrote a function to speed it up considerably (more than 2x faster). How could I improve it further, or can you come up with a faster routine?
function MyConvertDecToStrWithDot(Const n: UInt64): string;
Var a,b,x: Integer;
z,step: Integer;
l: SmallInt;
begin
Result := IntToStr(n);
if n < 1000 then Exit;
l := Length(Result);
a := l div 3;
b := l mod 3;
step := b+1;
z := 4;
if b <> 0 then begin
Insert('.',Result,step);
Inc(z,step);
end;
for x := 1 to (a-1) do begin
Insert('.',Result,z);
Inc(z,4);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
Var a: Integer;
s: string;
begin
PerfTimerInit;
for a := 1 to 1000000 do
s := MyConvertDecToStrWithDot(1234567890);
Memo1.lines.Add(PerfTimerStopMS.ToString);
caption := s;
end;
32-bit
Format: ~230ms
My function: ~79ms
64-bit
Format: ~440ms
My function: ~103ms
In my tests, the following is ever so slightly faster:
function ThousandsSepStringOf(Num: UInt64): string;
const
MaxChar = 30; // Probably, 26 is enough: 19 digits + 7 separators
var
Count: Integer;
Rem: UInt64;
Res: array[0..MaxChar] of Char;
WritePtr: PChar;
begin
WritePtr := #Res[MaxChar];
WritePtr^ := #0;
Count := 0;
while Num > 0 do
begin
DivMod(Num, 10, Num, Rem);
Dec(WritePtr);
WritePtr^ := Char(Byte(Rem) + Ord('0'));
Inc(Count);
if Count = 3 then
begin
Dec(WritePtr);
WritePtr^ := '.';
Count := 0;
end;
end;
if WritePtr^ = '.' then
Inc(WritePtr);
Count := MaxChar - ((NativeInt(WritePtr) - NativeInt(#Res)) shr 1);
SetLength(Result, Count);
Move(WritePtr^, PByte(Result)^, Count * SizeOf(Char));
end;
Tested with:
procedure TestHisCode;
Var
a: Integer;
s: string;
SW: TStopwatch;
begin
Writeln('His code');
SW := TStopwatch.StartNew;
for a := 1 to KLoops do
s := MyConvertDecToStrWithDot(1234567890);
Writeln(SW.ElapsedMilliseconds);
Writeln(s);
Writeln;
end;
procedure TestMyCode;
Var
a: Integer;
s: string;
SW: TStopwatch;
begin
Writeln('My code');
SW := TStopwatch.StartNew;
for a := 1 to KLoops do
s := ThousandsSepStringOf(1234567890);
Writeln(SW.ElapsedMilliseconds);
Writeln(s);
Writeln;
end;
and:
TestHisCode;
TestMyCode;
TestMyCode;
TestHisCode;
TestMyCode;
TestHisCode;
TestHisCode;
TestMyCode;
Haven't properly tested the performance of this, however it should be cross-platform and locale independent:
function Thousands(const ASource: string): string;
var
I, LLast: Integer;
begin
Result := ASource;
LLast := Length(Result);
I := LLast;
while I > 0 do
begin
if (LLast - I + 1) mod 3 = 0 then
begin
Insert(FormatSettings.ThousandSeparator, Result, I);
Dec(I, 2);
end
else
Dec(I);
end;
end;
Note: It obviously just works on integers
It's better to insert the separators directly while constructing the string instead of inserting separators later into the converted string because each insertion involves a lot of data movements and performance degradation. Besides avoid the division by 3 may improve performance a bit
This is what I get from my rusty Pascal after decades not using it
uses strutils;
function FormatNumber(n: integer): string;
var digit: integer;
count: integer;
isNegative: boolean;
begin
isNegative := (n < 0);
if isNegative then n := -n;
Result := '';
count := 3;
while n <> 0 do begin
digit := n mod 10;
n := n div 10;
if count = 0 then begin
Result := Result + '.';
count := 3;
end;
Result := Result + chr(ord('0') + digit);
dec(count);
end;
if isNegative then Result := Result + '-';
Result := reversestring(Result);
end;
See it in action: http://ideone.com/6O3e8w
It's also faster to just assign the characters directly instead of using concatenation operator/function like Victoria suggested. This is the improved version with only unsigned types
type string28 = string[28];
function FormatNumber(n: UInt64): string28;
var digit: integer;
length: integer;
count: integer;
c: char;
begin
count := 3;
length := 0;
while n <> 0 do begin
digit := n mod 10;
n := n div 10;
if count = 0 then begin
inc(length);
Result[length] := '.';
count := 3;
end;
inc(length);
Result[length] := chr(ord('0') + digit);
dec(count);
end;
for count := 1 to (length + 1) div 2 do begin
c := Result[count];
Result[count] := Result[length - count + 1];
Result[length - count + 1] := c;
end;
setlength(Result, length);
FormatNumber := Result;
end;
If the operation is done millions of times and is really a bottleneck after profiling, it's better to do in multiple threads along with SIMD

TParallel.For performance

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.
The key for this problem is correct partitioning and sharing as little as possible.
With this code it runs almost 4 times faster than the serial one.
const
WorkerCount = 4;
function GetWorker(index: Integer; const oddsArr: TArray<Integer>): TProc;
var
min, max: Integer;
begin
min := MaxArr div WorkerCount * index;
if index + 1 < WorkerCount then
max := MaxArr div WorkerCount * (index + 1) - 1
else
max := MaxArr - 1;
Result :=
procedure
var
i: Integer;
odds: Integer;
begin
odds := 0;
for i := min to max do
if Odd(ArrXY[i]) then
Inc(odds);
oddsArr[index] := odds;
end;
end;
procedure Parallel;
var
i: Integer;
oddsArr: TArray<Integer>;
workers: TArray<ITask>;
begin
odds := 0;
Ticks := TThread.GetTickCount;
SetLength(oddsArr, WorkerCount);
SetLength(workers, WorkerCount);
for i := 0 to WorkerCount-1 do
workers[i] := TTask.Run(GetWorker(i, oddsArr));
TTask.WaitForAll(workers);
for i := 0 to WorkerCount-1 do
Inc(odds, oddsArr[i]);
Ticks := TThread.GetTickCount - Ticks;
writeln('Parallel: ' + Ticks.ToString + 'ms, odds: ' + odds.ToString);
end;
You can write similar code with the TParallel.For but it still runs a bit slower (like 3 times faster than serial) than just using TTask.
Btw I used the function to return the worker TProc to get the index capturing right. If you run it in a loop in the same routine you capture the loop variable.
Update 19.12.2014:
Since we found out the critical thing is correct partitioning this can be put into a parallel for loop really easily without locking it on a particular data structure:
procedure ParallelFor(lowInclusive, highInclusive: Integer;
const iteratorRangeEvent: TProc<Integer, Integer>);
procedure CalcPartBounds(low, high, count, index: Integer;
out min, max: Integer);
var
len: Integer;
begin
len := high - low + 1;
min := (len div count) * index;
if index + 1 < count then
max := len div count * (index + 1) - 1
else
max := len - 1;
end;
function GetWorker(const iteratorRangeEvent: TProc<Integer, Integer>;
min, max: Integer): ITask;
begin
Result := TTask.Run(
procedure
begin
iteratorRangeEvent(min, max);
end)
end;
var
workerCount: Integer;
workers: TArray<ITask>;
i, min, max: Integer;
begin
workerCount := TThread.ProcessorCount;
SetLength(workers, workerCount);
for i := 0 to workerCount - 1 do
begin
CalcPartBounds(lowInclusive, highInclusive, workerCount, i, min, max);
workers[i] := GetWorker(iteratorRangeEvent, min, max);
end;
TTask.WaitForAll(workers);
end;
procedure Parallel4;
begin
odds := 0;
Ticks := TThread.GetTickCount;
ParallelFor(0, MaxArr-1,
procedure(min, max: Integer)
var
i, n: Integer;
begin
n := 0;
for i := min to max do
if Odd(ArrXY[i]) then
Inc(n);
AtomicIncrement(odds, n);
end);
Ticks := TThread.GetTickCount - Ticks;
writeln('ParallelEx: Stefan Glienke ' + Ticks.ToString + ' ms, odds: ' + odds.ToString);
end;
The key thing is to use a local variable for the counting and only at the end use the shared variable one time to add the sub total.
With OmniThreadLibrary from the SVN (this is not yet including in any official release), you can write this in a way which doesn't require interlocked access to the shared counter.
function CountParallelOTL: integer;
var
counters: array of integer;
numCores: integer;
i: integer;
begin
numCores := Environment.Process.Affinity.Count;
SetLength(counters, numCores);
FillChar(counters[0], Length(counters) * SizeOf(counters[0]), 0);
Parallel.For(0, MaxArr - 1)
.NumTasks(numCores)
.Execute(
procedure(taskIndex, value: integer)
begin
if Odd(ArrXY[value]) then
Inc(counters[taskIndex]);
end);
Result := counters[0];
for i := 1 to numCores - 1 do
Inc(Result, counters[i]);
end;
This, however, is still at best on par with the sequential loop and at worst a few times slower.
I have compared this with Stefan's solution (XE7 tasks) and with a simple XE7 Parallel.For with interlocked increment (XE7 for).
Results from my notebook with 4 hyperthreaded cores:
Serial: 49999640 odd elements found in 543 ms
Parallel (OTL): 49999640 odd elements found in 555 ms
Parallel (XE7 tasks): 49999640 odd elements found in 136 ms
Parallel (XE7 for): 49999640 odd elements found in 1667 ms
Results from my workstation with 12 hyperthreaded cores:
Serial: 50005291 odd elements found in 685 ms
Parallel (OTL): 50005291 odd elements found in 1309 ms
Parallel (XE7 tasks): 50005291 odd elements found in 62 ms
Parallel (XE7 for): 50005291 odd elements found in 3379 ms
There's a big improvement over System.Threading Paralell.For because there's no interlocked increment but the handcrafted solution is much much faster.
Full test program:
program ParallelCount;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SyncObjs,
System.Classes,
System.SysUtils,
System.Threading,
DSiWin32,
OtlCommon,
OtlParallel;
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;
function CountSerial: integer;
var
odds: integer;
begin
odds := 0;
for i := 0 to MaxArr-1 do
if Odd(ArrXY[i]) then
Inc(odds);
Result := odds;
end;
function CountParallelOTL: integer;
var
counters: array of integer;
numCores: integer;
i: integer;
begin
numCores := Environment.Process.Affinity.Count;
SetLength(counters, numCores);
FillChar(counters[0], Length(counters) * SizeOf(counters[0]), 0);
Parallel.For(0, MaxArr - 1)
.NumTasks(numCores)
.Execute(
procedure(taskIndex, value: integer)
begin
if Odd(ArrXY[value]) then
Inc(counters[taskIndex]);
end);
Result := counters[0];
for i := 1 to numCores - 1 do
Inc(Result, counters[i]);
end;
function GetWorker(index: Integer; const oddsArr: TArray<Integer>; workerCount: integer): TProc;
var
min, max: Integer;
begin
min := MaxArr div workerCount * index;
if index + 1 < workerCount then
max := MaxArr div workerCount * (index + 1) - 1
else
max := MaxArr - 1;
Result :=
procedure
var
i: Integer;
odds: Integer;
begin
odds := 0;
for i := min to max do
if Odd(ArrXY[i]) then
Inc(odds);
oddsArr[index] := odds;
end;
end;
function CountParallelXE7Tasks: integer;
var
i: Integer;
oddsArr: TArray<Integer>;
workers: TArray<ITask>;
workerCount: integer;
begin
workerCount := Environment.Process.Affinity.Count;
odds := 0;
Ticks := TThread.GetTickCount;
SetLength(oddsArr, workerCount);
SetLength(workers, workerCount);
for i := 0 to workerCount-1 do
workers[i] := TTask.Run(GetWorker(i, oddsArr, workerCount));
TTask.WaitForAll(workers);
for i := 0 to workerCount-1 do
Inc(odds, oddsArr[i]);
Result := odds;
end;
function CountParallelXE7For: integer;
var
odds: integer;
begin
odds := 0;
TParallel.For(0, MaxArr-1, procedure(I:Integer)
begin
if Odd(ArrXY[i]) then
TInterlocked.Increment(odds);
end);
Result := odds;
end;
procedure Count(const name: string; func: TFunc<integer>);
var
time: int64;
cnt: integer;
begin
time := DSiTimeGetTime64;
cnt := func();
time := DSiElapsedTime64(time);
Writeln(name, ': ', cnt, ' odd elements found in ', time, ' ms');
end;
begin
try
FillArray;
Count('Serial', CountSerial);
Count('Parallel (OTL)', CountParallelOTL);
Count('Parallel (XE7 tasks)', CountParallelXE7Tasks);
Count('Parallel (XE7 for)', CountParallelXE7For);
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
I think we discussed this before regarding OmniThreadLibrary. The main cause for the time being longer for the multithreaded solution is the overhead of TParallel.For compared to the time needed for the actual calculation.
A local variable won't be of any help here, while a global threadvar might solve the false sharing issue. Alas, you might not find a way to sum up all these treadvars after finishing the loop.
IIRC, the best approach is to chop the task in reasonable parts and work on a range of array entries for each iteration and increments a variable dedicated to that part. That alone won't solve the false sharing problem as that occurs even with distinct variables if they happen to be just part of the same cache line.
Another solution could be to write a class that handles a given slice of the array in a serial manner, act on multiple instances of this class in parallel and evaluate the results afterwards.
BTW: your code doesn't count the odds - it counts the evens.
And: there is a built-in function named Odd that usually is of better performance than the mod code you are using.
Ok, inspired by Stefan Glienke's answer I drafted a more reusable TParalleEx Class that instead of ITasks uses IFutures. The class is also somewhat modeled after the C# TPL with an aggregation delegate.This is just a first draft, but shows how the existing PPL can be extended with relative ease. This version now scales perfectly on my system - I would be happy if others could test it on different configurations. Thanks to all for your fruitful answers and comments.
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: TArray<Integer>;
type
TParallelEx<TSource, TResult> = class
private
class function GetWorker(body: TFunc<TArray<TSource>, Integer, Integer, TResult>; source: TArray<TSource>; min, max: Integer): TFunc<TResult>;
public
class procedure &For(source: TArray<TSource>;
body: TFunc<TArray<TSource>, Integer, Integer, TResult>;
aggregator: TProc<TResult>);
end;
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 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;
const
WorkerCount = 4;
function GetWorker(index: Integer; const oddsArr: TArray<Integer>): TProc;
var
min, max: Integer;
begin
min := MaxArr div WorkerCount * index;
if index + 1 < WorkerCount then
max := MaxArr div WorkerCount * (index + 1) - 1
else
max := MaxArr - 1;
Result :=
procedure
var
i: Integer;
odds: Integer;
begin
odds := 0;
for i := min to max do
if ArrXY[i] mod 2 <> 0 then
Inc(odds);
oddsArr[index] := odds;
end;
end;
procedure Parallel2;
var
i: Integer;
oddsArr: TArray<Integer>;
workers: TArray<ITask>;
begin
odds := 0;
Ticks := TThread.GetTickCount;
SetLength(oddsArr, WorkerCount);
SetLength(workers, WorkerCount);
for i := 0 to WorkerCount-1 do
workers[i] := TTask.Run(GetWorker(i, oddsArr));
TTask.WaitForAll(workers);
for i := 0 to WorkerCount-1 do
Inc(odds, oddsArr[i]);
Ticks := TThread.GetTickCount - Ticks;
writeln('Parallel: Stefan Glienke ' + Ticks.ToString + ' ms, odds: ' + odds.ToString);
end;
procedure parallel3;
var
sum: Integer;
begin
Ticks := TThread.GetTickCount;
TParallelEx<Integer, Integer>.For( ArrXY,
function(Arr: TArray<Integer>; min, max: Integer): Integer
var
i: Integer;
res: Integer;
begin
res := 0;
for i := min to max do
if Arr[i] mod 2 <> 0 then
Inc(res);
Result := res;
end,
procedure(res: Integer) begin sum := sum + res; end );
Ticks := TThread.GetTickCount - Ticks;
writeln('ParallelEx: Markus Joos ' + Ticks.ToString + ' ms, odds: ' + odds.ToString);
end;
{ TParallelEx<TSource, TResult> }
class function TParallelEx<TSource, TResult>.GetWorker(body: TFunc<TArray<TSource>, Integer, Integer, TResult>; source: TArray<TSource>; min, max: Integer): TFunc<TResult>;
begin
Result := function: TResult
begin
Result := body(source, min, max);
end;
end;
class procedure TParallelEx<TSource, TResult>.&For(source: TArray<TSource>;
body: TFunc<TArray<TSource>, Integer, Integer, TResult>;
aggregator: TProc<TResult>);
var
I: Integer;
workers: TArray<IFuture<TResult>>;
workerCount: Integer;
min, max: integer;
MaxIndex: Integer;
begin
workerCount := TThread.ProcessorCount;
SetLength(workers, workerCount);
MaxIndex := length(source);
for I := 0 to workerCount -1 do
begin
min := (MaxIndex div WorkerCount) * I;
if I + 1 < WorkerCount then
max := MaxIndex div WorkerCount * (I + 1) - 1
else
max := MaxIndex - 1;
workers[i]:= TTask.Future<TResult>(GetWorker(body, source, min, max));
end;
for i:= 0 to workerCount-1 do
begin
aggregator(workers[i].Value);
end;
end;
begin
try
FillArray;
Serial;
Parallel;
Parallel2;
Parallel3;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
Regarding the task of using local variables to collect the sums and then collect them at the end, you can use a separate array for that purpose:
var
sums: array of Integer;
begin
SetLength(sums, MaxArr);
for I := 0 to MaxArr-1 do
sums[I] := 0;
Ticks := TThread.GetTickCount;
TParallel.For(0, MaxArr-1,
procedure(I:Integer)
begin
if ArrXY[i] mod 2 = 0 then
Inc(sums[I]);
end
);
Ticks := TThread.GetTickCount - Ticks;
odds := 0;
for I := 0 to MaxArr-1 do
Inc(odds, sums[i]);
writeln('Parallel - false odds: ' + Ticks.ToString + 'ms, odds: ' + odds.ToString);
end;

Strange result in multithreading using Delphi XE

I am trying to use multithreading in Delphi XE.
The task is following I have to create 4 threads. Each thread draw colored circle in Paintbox at predefined area, For example FIRST thread draw only red circles in first quoter of the Paintbox, the SECOND thread draw only yellow circles in the second quoter, and so on.
I have defined following class
const
NumberOfIterations = 100000;
NumberOfTreads = 4;
TCalcThread = class(TThread)
private
FIdx: Integer;
FHits: Cardinal;
V: array of Integer;
xPaintBox1: TPaintBox;
protected
procedure Execute; override;
public
constructor Create(Idx: Integer; vPaintBox: TPaintBox);
property Hits: Cardinal read FHits;
end;
In main code I do the following:
procedure TForm11.Button1Click(Sender: TObject);
var
thrarr: array[0..NumberOfTreads - 1] of TCalcThread;
hndarr: array[0..NumberOfTreads - 1] of THandle;
i, a, t: Integer;
x, y: Integer;
begin
caption := '';
PaintBox1.Canvas.Brush.Color := clWhite;
PaintBox1.Canvas.fillrect(PaintBox1.Canvas.ClipRect);
for i := 0 to NumberOfTreads - 1 do
begin
thrarr[i] := TCalcThread.Create(i, PaintBox1);
hndarr[i] := thrarr[i].Handle;
end;
WaitForMultipleObjects(NumberOfTreads, #hndarr, True, INFINITE);
for i := 0 to NumberOfTreads - 1 do
thrarr[i].Free;
end;
The thread Create and Execute methods are defined as following:
constructor TCalcThread.Create(Idx: Integer; vPaintBox: TPaintBox);
begin
FIdx := Idx;
FHits := 0;
xPaintBox1 := vPaintBox;
case FIdx of
0: xPaintBox1.Canvas.Pen.Color := clRed;
1: xPaintBox1.Canvas.Pen.Color := clYellow;
2: xPaintBox1.Canvas.Pen.Color := clBlue;
3: xPaintBox1.Canvas.Pen.Color := clMoneyGreen;
end;
xPaintBox1.Canvas.Brush.Color := xPaintBox1.Canvas.Pen.Color;
inherited Create(False);
end;
procedure TCalcThread.Execute;
var
i, start, finish: Integer;
x, y: Integer;
begin
start := (NumberOfIterations div NumberOfTreads) * FIdx;
finish := start + (NumberOfIterations div NumberOfTreads) - 1;
for i := start to finish do
begin
case FIdx of
0: begin
x := Random(200) + 1;
end;
1: begin
x := Random(200) + 201;
end;
2: begin
x := Random(200) + 401;
end;
3: begin
x := Random(200) + 601;
end;
end;
y := Random((xPaintBox1.height )) + 1;
xPaintBox1.Canvas.Ellipse(X - 5, Y - 5, X + 5, Y + 5);
end;
end;
As a result I am getting a few circles in three areas with the same color, and a lot of circles in one area (the same color). What I am doing wrong?

Best way to replace every third character in a string in delphi

What's the most efficient way to replace every third character of the same type in a string?
I have a string like this:
str := 'c1'#9'c2'#9'c3'#9'c4'#9'c5'#9'c6'#9'
I want to replace every third #9 by #13#10, so that i get:
str1 := 'c1'#9'c2'#9'c3'#13#10'c4'#9'c5'#9'c6'#13#10'
I would do this in this way:
i:=0;
newStr:='';
lastPos := Pos(str,#9);
while lastPos > 0 do begin
if i mod 3 = 2 then begin
newStr := newStr + Copy(str,1,lastPos-1) + #13#10;
end else begin
newStr := newStr + Copy(str,1,lastPos);
end;
str := Copy(str,lastPos+1,MaxInt);
i := i + 1;
lastPos := Pos(str,#9);
end;
newStr := Copy(str,1,MaxInt);
But thats a lot of copying. Is there a string manipulation function to do this?
I think the problem as stated doesn't match the code you provided. Is every third character a #9? If so, do you want to change every third appearance of #9 for #13#10?
If so, I would do it this way:
function test(str: string): string;
var
i, c, l: integer;
begin
l := Length(str);
SetLength(Result, l + l div 9);
c := 1;
for i := 1 to l do
begin
if (i mod 9 = 0) and (i > 0) then
begin
Result[c] := #13;
Inc(c);
Result[c] := #10;
end
else
Result[c] := str[i];
Inc(c);
end;
end;
I actually have no idea if this function performs well. But given that the constraints aren't clear, I guess so.
If the position of the #9 character is unknown then this solution won't work at all.
Edit: as David points out, this is not nearly equivalent to the original code posted. This seems to work, but it requires two passes on the original string. The thing is, to know if its more efficient or not we need to know more about the input and context.
function OccurrencesOfChar(const S: string; const C: char): integer;
var
i: integer;
begin
result := 0;
for i := 1 to Length(S) do
if S[i] = C then
inc(result);
end;
function Test(str: string): string;
var
len, n, C, i: integer;
begin
C := 1;
len := Length(str);
n := OccurrencesOfChar(str, #9);
SetLength(result, len + n div 3);
n := 1;
for i := 1 to len do
begin
if str[i] = #9 then
begin
if n mod 3 = 0 then
begin
result[C] := #13;
inc(C);
result[C] := #10;
end
else
result[C] := #9;
Inc(n);
end
else
result[C] := str[i];
inc(C);
end;
end;
I expect this question will be closed, but just for fun, that would be my proposal.
Function Replace(const Instr:String;Re:Char;const ReWith:String):String;
var
i,o,cnt,l:Integer;
begin
cnt:=0;
o:=0;
SetLength(Result,Length(Instr)*Length(ReWith));// just for security
for I := 1 to Length(Instr) do
begin
if Instr[i]=Re then inc(cnt);
if cnt=3 then
begin
for l := 1 to Length(ReWith) do
begin
inc(o);
Result[o] := ReWith[l];
end;
cnt := 0;
end
else
begin
inc(o);
Result[o] := Instr[i];
end;
end;
SetLength(Result,o);
end;
procedure TForm3.Button1Click(Sender: TObject);
begin
Edit2.Text := Replace(Edit1.Text,'A','xxx')
end;
I would probably do something like this (coded in the browser). It only needs one string resize and should have less movement of data around. I exit when I have made the last replacement or if it didn't need any:
procedure ReplaceXChar(var aStringToReplace: string; const aIteration:
Integer; const aChar: Char; const aReplacement: string);
var
replaceCount: Integer;
cntr: Integer;
outputCntr: Integer;
lengthToReplace: Integer;
begin
// Find the number to replace
replaceCount := 0;
for cntr := 1 to Length(aStringToReplace) do
begin
if aStringToReplace[cntr] = aChar then
Inc(replaceCount);
end;
if replaceCount >= aIteration then
begin
// Now replace them
lengthToReplace := Length(aReplacement);
cntr := Length(aStringToReplace);
SetLength(aStringToReplace, cntr +
(replaceCount div aIteration) * (lengthToReplace - 1));
outputCntr := Length(aStringToReplace);
repeat
if aStringToReplace[cntr] = aChar then
begin
if (replaceCount mod aIteration) = 0 then
begin
Dec(outputCntr, lengthToReplace);
Move(aReplacement[1], aStringToReplace[outputCntr+1],
lengthToReplace * SizeOf(Char));
end
else
begin
aStringToReplace[outputCntr] := aStringToReplace[cntr];
Dec(outputCntr);
end;
Dec(replaceCount);
end
else
begin
aStringToReplace[outputCntr] := aStringToReplace[cntr];
Dec(outputCntr);
end;
Dec(cntr);
until replaceCount = 0;
end;
end;
Usage would be like this:
var
myString: String;
begin
myString := 'c1'#9'c2'#9'c3'#9'c4'#9'c5'#9'c6'#9;
ReplaceXChar(myString, 3, #9, #13#10);
ShowMessage(myString);
end;

Resources