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

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

Related

Remove non-numeric characters from string in Delphi

I have these three functions that successfully remove all non-numeric characters from a given string:
The first function loops through the characters of the input string, and if the current character is a number, it adds it to a new string that is returned as the result of the function.
function RemoveNonNumericChars(const s: string): string;
begin
Result := '';
for var i := 1 to Length(s) do
begin
if s[i] in ['0'..'9'] then
Result := Result + s[i];
end;
end;
The second function loops through the characters of the input string from right to left, and if the current character is not a number, it uses the Delete function to remove it from the string
function RemoveNonNumericChars(const s: string): string;
begin
Result := s;
for var i := Length(Result) downto 1 do
begin
if not(Result[i] in ['0'..'9']) then
Delete(Result, i, 1);
end;
end;
The third function uses a regular expression to replace all non-numeric characters with nothing, thus removing them. TRegEx is from the System.RegularExpressions unit.
function RemoveNonNumericChars(const s: string): string;
begin
var RegEx := TRegEx.Create('[^0-9]');
Result := RegEx.Replace(s, '');
end;
All three of them do what I need, but I want to know if there is maybe a built-in function in Delphi for this... Or maybe even a better way to do it than the way I'm doing it. What's the best and/or fastest way to remove non-numeric characters from a string in Delphi?
Both your approaches are slow because you constantly change the length of the string. Also, they only recognise Arabic digits.
To solve the performance issue, preallocate the maximum result length:
function RemoveNonDigits(const S: string): string;
begin
SetLength(Result, S.Length);
var LActualLength := 0;
for var i := 1 to S.Length do
if CharInSet(S[i], ['0'..'9']) then
begin
Inc(LActualLength);
Result[LActualLength] := S[i];
end;
SetLength(Result, LActualLength);
end;
To support non-Arabic digits, use the TCharacter.IsDigit function:
function RemoveNonDigits(const S: string): string;
begin
SetLength(Result, S.Length);
var LActualLength := 0;
for var i := 1 to S.Length do
if S[i].IsDigit then
begin
Inc(LActualLength);
Result[LActualLength] := S[i];
end;
SetLength(Result, LActualLength);
end;
To optimise even further, as suggested by Stefan Glienke, you can bypass the RTL's string handling machinery and write each character directly with some loss of code readability:
function RemoveNonDigits(const S: string): string;
begin
SetLength(Result, S.Length);
var ResChr := PChar(Result);
var LActualLength := 0;
for var i := 1 to S.Length do
if CharInSet(S[i], ['0'..'9']) then
begin
Inc(LActualLength);
ResChr^ := S[i];
Inc(ResChr);
end;
SetLength(Result, LActualLength);
end;
Benchmark
Just for fun I did a very primitive benchmark on random input strings with length < 100 and about 24% chance of a char being a digit:
program Benchmark;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.RegularExpressions, Windows;
function OP1(const s: string): string;
begin
Result := '';
for var i := 1 to Length(s) do
begin
if s[i] in ['0'..'9'] then
Result := Result + s[i];
end;
end;
function OP2(const s: string): string;
begin
Result := s;
for var i := Length(Result) downto 1 do
begin
if not(Result[i] in ['0'..'9']) then
Delete(Result, i, 1);
end;
end;
function OP3(const s: string): string;
begin
var RegEx := TRegEx.Create('[^0-9]');
Result := RegEx.Replace(s, '');
end;
function AR1(const S: string): string;
begin
SetLength(Result, S.Length);
var LActualLength := 0;
for var i := 1 to S.Length do
if CharInSet(S[i], ['0'..'9']) then
begin
Inc(LActualLength);
Result[LActualLength] := S[i];
end;
SetLength(Result, LActualLength);
end;
function AR2(const S: string): string;
begin
SetLength(Result, S.Length);
var ResChr := PChar(Result);
var LActualLength := 0;
for var i := 1 to S.Length do
if CharInSet(S[i], ['0'..'9']) then
begin
Inc(LActualLength);
ResChr^ := S[i];
Inc(ResChr);
end;
SetLength(Result, LActualLength);
end;
function AR3(const S: string): string;
begin
SetLength(Result, S.Length);
var ResChr := PChar(Result);
for var i := 1 to S.Length do
if CharInSet(S[i], ['0'..'9']) then
begin
ResChr^ := S[i];
Inc(ResChr);
end;
SetLength(Result, ResChr - PChar(Result));
end;
function RandomInputString: string;
begin
SetLength(Result, Random(100));
for var i := 1 to Result.Length do
Result[i] := Chr(Ord('0') + Random(42));
end;
begin
Randomize;
const N = 1000000;
var Data := TArray<string>(nil);
SetLength(Data, N);
for var i := 0 to N - 1 do
Data[i] := RandomInputString;
var f, c0, cOP1, cOP2, cOP3, cAR1, cAR2, cAR3: Int64;
QueryPerformanceFrequency(f);
QueryPerformanceCounter(c0);
for var i := 0 to High(Data) do
OP1(Data[i]);
QueryPerformanceCounter(cOP1);
Dec(cOP1, c0);
QueryPerformanceCounter(c0);
for var i := 0 to High(Data) do
OP2(Data[i]);
QueryPerformanceCounter(cOP2);
Dec(cOP2, c0);
QueryPerformanceCounter(c0);
for var i := 0 to High(Data) do
OP3(Data[i]);
QueryPerformanceCounter(cOP3);
Dec(cOP3, c0);
QueryPerformanceCounter(c0);
for var i := 0 to High(Data) do
AR1(Data[i]);
QueryPerformanceCounter(cAR1);
Dec(cAR1, c0);
QueryPerformanceCounter(c0);
for var i := 0 to High(Data) do
AR2(Data[i]);
QueryPerformanceCounter(cAR2);
Dec(cAR2, c0);
QueryPerformanceCounter(c0);
for var i := 0 to High(Data) do
AR3(Data[i]);
QueryPerformanceCounter(cAR3);
Dec(cAR3, c0);
Writeln('Computations per second:');
Writeln('OP1: ', Round(N / (cOP1 / f)));
Writeln('OP2: ', Round(N / (cOP2 / f)));
Writeln('OP3: ', Round(N / (cOP3 / f)));
Writeln('AR1: ', Round(N / (cAR1 / f)));
Writeln('AR2: ', Round(N / (cAR2 / f)));
Writeln('AR3: ', Round(N / (cAR3 / f)));
Readln;
end.
Result:
Computations per second:
OP1: 1398134
OP2: 875116
OP3: 39162
AR1: 3406172
AR2: 4063260
AR3: 4032343
As you can see, in this test at least, regular expressions are by far the slowest approach. And preallocating makes a major difference, while avoiding the _UniqueStringU issue appears to make only a relatively minor improvement.
But even with the very slow RegEx approach, you can do 40 000 calls per second. On my 13-year-old computer.

Convert String to two chars and reverse the two chars

Hello I want to split a simple string into a group of two characters then reverse them.
For example MyStringToBeReversed to two charachters each My St ri ng To Be Re ve rs ed
then reverse each splited two charachters to :
St := ts
ri := ir
etc ...
Here's the code I'm using but it only displays one charachter on memo lines
procedure TForm7.Button1Click(Sender: TObject);
var
MyString: string;
i: integer;
k: integer;
j: integer;
begin
MyString:= Edit1.Text;
for i:= 1 to length(MyString) do
Memo1.Lines.Add(MyString[i]);
// Move(Str[1], MyString, Length(Str));
end;
end.
This should do what you need
function ReverseEachTwoChars(AInput : string) : string;
var
i : integer;
begin
if(Odd(Length(AInput))) then
begin
SetLength(AInput, Length(AInput) + 1);
AInput[Length(AInput)] := 'A';
end;
SetLength(Result, Length(AInput));
i := 1;
while(i < Length(Result)) do
begin
Result[i] := AInput[i + 1];
Result[i + 1] := AInput[i];
Inc(i, 2);
end;
end;
Test:
ShowMessage(ReverseEachTwoChars('010203040506'));
Output:
102030405060

Delphi 2007 AsyncMultiSync Does not work

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.

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;

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