How to speed up this conversion function? - performance

I have this function called IntToStrLen which converts an integer to a string of a padded size. For example, an integer value of 42 with a size of 4 would result in a string __42 (padded with a given character, space by default).
The problem is when this function is used in bulk (for example 1,000,000 times in a loop), it adds extra weight onto the loop. The loop I'm using this in, without this function, takes about 20 seconds, but with this function, I'm still waiting as of right now for this function to complete, after about 5 minutes.
How can I speed up the following function?
function IntToStrLen(const Value: Integer; const Len: Integer;
const Fill: String = ' '): String;
var
T: String;
begin
Result:= IntToStr(Value); //convert result
if Length(Result) > Len then
Result:= Copy(Result, 1, Len) //forcefully truncate
else if Length(Result) < Len then begin
T:= '';
while Length(T) < (Len - Length(Result)) do //fill space with character
T:= T + Fill;
Result:= T + Result; //return combination
end;
end;

The absolute number one change that you can make here is to avoid heap allocations. Your code presently has multiple heap allocations. Your goal here is to write a function with zero heap allocations.
That means you need the caller to allocate and provide the buffer. Typically they will do so with a stack allocated buffer, which hence costs nothing to allocate (or deallocate). If the caller really needs a string, which is always allocated on the heap, then the caller can wrap your function with a call to SetString.
The function prototype might look like this:
procedure IntToStrLen(const Value, Len: Integer; var Buffer: array of Char;
const Fill: Char = ' ');
The first point to stress here is that Fill must be a Char. Your use of string is inefficient and allows the caller to call a fill "character" with length not equal to one. Doing so would of course break your function because it would return a value with length not equal to Len.
Do note also that the implementation must not call IntToStr because that involves heap allocation. So you need to write your own heap allocation free integer to decimal text conversion code because, astonishingly, the RTL does not offer such functionality. When I do this I use code like so:
procedure DivMod(Dividend, Divisor: Cardinal;
out Quotient, Remainder: Cardinal);
{$IFDEF CPUX86}
asm
PUSH EBX
MOV EBX,EDX
XOR EDX,EDX
DIV EBX
MOV [ECX],EAX
MOV EBX,Remainder
MOV [EBX],EDX
POP EBX
end;
{$ELSE IF Defined(CPUX64)}
asm
.NOFRAME
MOV EAX,ECX
MOV ECX,EDX
XOR EDX,EDX
DIV ECX
MOV [R8],EAX
MOV [R9],EDX
end;
{$ELSE}
{$Message Error 'Unrecognised platform.'}
{$ENDIF}
function CopyIntegerToCharBuffer(const Value: Integer;
var Buffer: array of Char): Integer;
var
i, j: Integer;
val, remainder: Cardinal;
negative: Boolean;
tmp: array [0..15] of Char;
begin
negative := Value<0;
val := abs(Value);
Result := 0;
repeat
DivMod(val, 10, val, remainder);
tmp[Result] := Chr(remainder + ord('0'));
inc(Result);
until val=0;
if negative then begin
tmp[Result] := '-';
inc(Result);
end;
Assert(Result<=Length(Buffer));
i := 0;
j := Result-1;
while i<Result do begin
Buffer[i] := tmp[j];
inc(i);
dec(j);
end;
end;
Now you can make decimal text representations of integers without touching the heap. From there it's a short way to your function.
procedure IntToStrLen(const Value, Len: Integer; var Buffer: array of Char;
const Fill: Char = ' ');
var
tmp: array [0..15] of Char;
i, N: Integer;
begin
Assert(Length(Buffer)>=Len);
N := CopyIntegerToCharBuffer(Value, tmp);
if N>=Len then begin
Move(tmp, Buffer, SizeOf(Char)*Len);
end else begin
for i := 0 to Len-N-1 do begin
Buffer[i] := Fill;
end;
Move(tmp, Buffer[Len-N], SizeOf(Char)*N);
end;
end;
At this point you'll have gained the bulk of the available performance benefits. From here on in you will be into diminishing returns. You could micro-optimise CopyIntegerToCharBuffer as is done in SysUtils._IntToStr32 for instance. Beyond that I'm sure the implementation of IntToStrLen could be optimised with judicious use of assembler. But such optimisations will yield nothing like the benefit you have gained so far from avoiding the heap.
Of course, all this assumes that you have correctly identified your performance bottleneck. It's all too easy to assume that you know where the performance bottleneck is by statically analysing the code. Unless you've actually profiled it carefully expect to find that your intuition is a poor judge of where to invest optimisation effort.

Try this variant
(and you don't treat negative numbers)
function IntToStrLen(const Value: Integer; const Len: Integer;
const Fill: Char = ' '): String;
var
T: String;
begin
Result:= IntToStr(Value);
if Length(Result) > Len then
SetLength(Result, Len) //forcefully truncate
else if Length(Result) < Len then
Result := StringOfChar(Fill, Len - Length(Result)) + Result;
end;
P.S. So strange truncation (2014=>20) - is what you really want?

Related

Dynamic array in Turbo Pascal

I am working on my school project and I would like to use Dynamic (not static) array. I worked with ObjectPascal, so I am used to some syntax. But now I am programming in the old TurboPascal (I am using Turbo Pascal 7 for Windows).
It doesn't seem to know the ObjectPascal, so I thought, that you Turbo Pascal doesn't know dynamic arrays.
Could anyone tell me, if my theory is right or not? I tried to google, but I was not succesfull.
Basicly I am asking "how is it with dynamic arrays in Turbo Pascal 7" ?
Thank you for all reactions.
As MartynA says, there is no dynamic array type in Turbo Pascal. You need to manually allocate memory using pointers, and be careful if you use rangechecks.
Typically you define an array type
TYPE
TArrayT = array[0.. ((65535-spillbytes) div sizeof(T))-1] of T;
where spillbytes is a constant for a small deduction because you can't use the whole 64k, see what the compiler accepts. (Probably this deduction is for heapmanager structures inside the 64k block)
Then you define a pointer
PArrayT= ^TArrayT;
and a variable to it
var
P : PArrayT;
and you allocate nrelement elements using getmem;
getmem(P,SizeOf(T) * nrelements);
and optionally fill them with zero to initialize them:
fillchar(p^,SizeOf(T) * nrelements,#0);
You can access elements using
p^[index]
to free them, use freemem using the exact opposite of the getmem line.
freemem(P,Sizeof(T)*nrelements);
Which means you have to save the allocated number of elements somewhere. This was fixed/solved in Delphi and FPC.
Also keep in mind that you can't find bugs with rangechecking anymore.
If you want arrays larger than 64k, that is possible, but only with constraints, and it matters more which exact TP target (dos, dos-protected or Windows you use) I advise you to search for the online SWAG archive that has many examples. And of course I would recommend to go to FreePascal/Lazarus too where you can simply do:
var x : array of t;
begin
setlength(x,1000000);
and be done with it without additional lines and forget about all of this nonsense.
I'm using Turbo Pascal 5.5 and to create a dynamic array, perhaps the trick is to declare an array with zero dimension as follows:
dArray = array [0..0] of integer;
And then declare a pointer to that array:
pArray = ^dArray ;
And finally, create a pointer variable:
ArrayPtr : pArray;
You can now reference the pointer variable ArrayPtr as follows:
ArrayPtr^[i]; { The index 'i' is of type integer}
See the complete example below:
{
Title: dynarr.pas
A simple Pascal program demonstrating dynamic array.
Compiled and tested with Turbo Pascal 5.5.
}
program dynamic_array;
{Main Program starts here}
type
dArray = array [0..0] of integer;
pArray = ^dArray ;
var
i : integer;
ArrayPtr : pArray;
begin
for i := 0 to 9 do { In this case, array index starts at 0 instead of 1. }
ArrayPtr^[i] := i + 1;
writeln('The Dynamic Array now contains the following:');
writeln;
for i := 0 to 9 do
writeln(ArrayPtr^[i]);
end.
In this example, we have declared the array as:
array[0..0] of integer;
Therefore, the index starts at 0 and if we have n elements, the last element is at index n-1 which is similar to array indexing in C/C++.
Regular Pascal arrays start at 1 but for this case, it starts at 0.
unit Vector;
interface
const MaxVector = 8000;
// 64 k div SizeOf(float); number of float-values that fit in 64 K of stack
VectorError: boolean = False;
// toggle if error occurs. Calling routine can handle or abort
type
VectorStruc = record
Length: word;
Data: array [1..MaxVector] of float;
end;
VectorTyp = ^VectorStruc;
procedure CreateVector(var Vec: VectorTyp; Length: word; Value: float);
{ Generates a vector of length Length and sets all elements to Value }
procedure DestroyVector(var Vec: VectorTyp);
{ release memory occupied by vector }
procedure SetVectorElement(var Vec: VectorTyp; n: word; c: float);
function GetVectorElement(const Vec: VectorTyp; n: word): float;
implementation
var ch: char;
function WriteErrorMessage(Text: string): char;
begin
Write(Text);
Read(WriteErrorMessage);
VectorError := True; // toggle the error marker
end;
procedure CreateVector(var Vec: VectorTyp; Length: word; Value: float);
var
i: word;
begin
try
GetMem(Vec, Length * SizeOf(float) + SizeOf(word) + 6);
except
ch := WriteErrorMessage(' Not enough memory to create vector');
exit;
end;
Vec^.Length := Length;
for i := 1 to Length do
Vec^.Data[i] := Value;
end;
procedure DestroyVector(var Vec: VectorTyp);
var
x: word;
begin
x := Vec^.Length * SizeOf(float) + SizeOf(word) + 6;
FreeMem(Vec, x);
end;
function VectorLength(const Vec: VectorTyp): word;
begin
VectorLength := Vec^.Length;
end;
function GetVectorElement(const Vec: VectorTyp; n: word): float;
var
s1, s2: string;
begin
if (n <= VectorLength(Vec)) then
GetVectorElement := Vec^.Data[n]
else
begin
Str(n: 4, s1);
Str(VectorLength(Vec): 4, s2);
ch := WriteErrorMessage(' Attempt to read non-existent vector element No ' +
s1 + ' of ' + s2);
end;
end;
procedure SetVectorElement(var Vec: VectorTyp; n: word; C: float);
begin
if (n <= VectorLength(Vec)) then
Vec^.Data[n] := C
else
ch := WriteErrorMessage(' Attempt to write to non-existent vector element');
end;
end.
As long as your data fit on the stack, i.e., are smaller than 64 kB, the task is relatively simple. The only thing I don't know is where the 6 bit of extra size go, they are required, however.

Pascal, reading unknown number of integers

My question is how can I read some number of integers that user enters on standard input, and place them in array.However I don't how many numbers user will enter and i can't ask him that? User enters numbers in one line.
Okay i have just one more answer i would like to add.Thanks all for your help this is code written based on suggestions.I added a line for writting array backwards just for you can see that it has readed well.
program backo;
var niz:array [1..100] of integer;
n, i:integer;
begin
i:=1;
writeln('enter elements of array');
read(niz[i]);
while not eoln do
begin
i:=i+1;
read(niz[i]);
end;
for n:=i downto 1 do
writeln(niz[n]);
end.
Ok, based on comments there is three ways demonstrated:
program readmultiint;
{$mode objfpc}{$H+}
uses
StrUtils;
const
CMaxValues = 3;
var
s: string;
darr: array of Integer;
sarr: array [0..CMaxValues-1] of Integer;
i, cnt: Integer;
begin
// Dynamic array using WordCount
Writeln('Enter values:');
Readln(s);
cnt := WordCount(s, StdWordDelims);
SetLength(darr, cnt); // Allocate room for values
for i := 0 to cnt - 1 do
Val(ExtractWord(i + 1, s, StdWordDelims), darr[i]);
for i in darr do
Writeln(i);
// Dynamic array usin EOLN
SetLength(darr, 0);
Writeln('Enter values:');
while not eoln do
begin
SetLength(darr, Length(darr) + 1); // Expand array for next value
Read(darr[High(darr)]);
end;
Readln; // Read <Enter> itself
for i in darr do
Writeln(i);
// Static array
cnt := 0;
Writeln('Enter values:');
while (not eoln) and (cnt < CMaxValues) do // Reads not more then CMaxValues values
begin
Read(sarr[cnt]);
Inc(cnt);
end;
Readln; // Read <Enter> itself
for i := 0 to cnt-1 do
Writeln(sarr[i]);
end.
Feel free to use one of them or provide your own :)
PS: Some readings:
Dynamic arrays
Val procedure
for-in loop

How many times does one number divide into another, and how much is left over?

I need an algorithm in Delphi to generate partitions for a specified integer value.
Example: for 13 if 5 is specified as the max value for partition it will give 5,5,3; if 4 is specified as max partition value the result should be 4,4,4,1, and so on.
It's simple enough to solve the problem using div and mod. Here's an example program that I don't think needs any further explanation:
program IntegerPartitions;
{$APPTYPE CONSOLE}
function Partitions(const Total, Part: Integer): TArray<Integer>;
var
Count: Integer;
Rem: Integer;
i: Integer;
begin
Assert(Total>0);
Assert(Part>0);
Count := Total div Part;
Rem := Total mod Part;
if Rem=0 then
SetLength(Result, Count)
else
SetLength(Result, Count+1);
for i := 0 to Count-1 do
Result[i] := Part;
if Rem<>0 then
Result[Count] := Rem;
end;
var
Value: Integer;
begin
for Value in Partitions(13, 5) do
Writeln(Value);
Readln;
end.

Typecasting WideString breaks array of widechar

I use this procedure to ENUM the keys into a TNTListView (UNICODE) in Delphi 7
procedure TForm1.TntButton1Click(Sender: TObject);
var
k : HKEY;
Buffer : array of widechar;
i : Integer;
iRes : Integer;
BuffSize : DWORD;
item : TTNTListItem;
WS : WideString;
begin
if RegOpenKeyExW (HKEY_CURRENT_USER, 'Software', 0, KEY_READ, K) = ERROR_SUCCESS then begin
try
i := 0;
BuffSize := 1;
while true do begin
SetLength (Buffer, BuffSize);
iRes := RegEnumKeyW(k, I, #Buffer[0], BuffSize);
if iRes = 259 then break;
if iRes = 234 then begin
inc (BuffSize);
continue;
end;
messageboxw (0, #Buffer[0], '', 0);
item := TNTListView1.Items.Add;
item.Caption := WideString (Buffer); // BREAKS IT
{ SOLUTION }
SetLength (WS, BuffSize - 1);
CopyMemory (#WS[1], #Buffer[0], (BuffSize * 2));
{ .... }
inc (i);
BuffSize := 1;
end;
finally
RegCloseKey (k);
SetLength (Buffer, 0);
end;
end;
end;
I see that most of the listviewitems are trimmed! However if I show the Buffer in the messagebox it shows the complete string in the right length. Is this a Bug of the listview or am I missing something like a NULL CHAR (or even 2)?
Thanks for help.
EDIT: I just noticed that the Buffer get's trimmed into half when I cast it to a widestring.
EDIT2: No bug in the listview. The WideString Cast breaks the string somehow and / or doesn't detect the NULL CHAR(s).
You are right - casting array of WideChar to WideString halves the string length in pre-Unicode Delphi's.
Tested on Delphi 2007:
var
A: array of WideChar;
begin
SetLength(A, 4);
ShowMessage(IntToStr(Length(WideString(A)))); // 2
end;
A quick view on the above code in debugger CPU window shows that typecasting array of WideChar-> WideString does not result in any data conversion; internal WideString format stores the string size (i.e. the number of bytes) in the same place where Delphi strings or dynarrays store length. As a result typecasting halves string length.

How to convert a tree recursive function ( or algorithm ) to a loop one?

I have written a recursive Tree Function in pascal ( or delphi ) but i had an 'Out of Memory' message when I ran it.
I need to turn the Calculate recursive function in this code to non-recursive function, can you tell me how please :
program testing(input, output);
type
ptr = ^tr;
tr = record
age: byte;
left, right: ptr;
end;
var
topper: ptr;
total, day: longint;
procedure mycreate(var t: ptr);
var
temp:ptr;
begin
new(temp);
temp^.age := 1;
temp^.left := nil;
temp^.right := nil;
t := temp;
end;
procedure gooneday(var t: ptr);
begin
if t^.age <> 5 then
begin
if t^.age = 2 then
mycreate(t^.left)
else if t^.age = 3 then
mycreate(t^.right);
t^.age := t^.age + 1;
total := total + 1;
end;
end;
procedure calculate(var crnt: ptr);
begin
if crnt <> nil then
begin
gooneday(crnt);
calculate(crnt^.left);
calculate(crnt^.right);
end;
end;
begin
total := 0;
mycreate(topper);
day := 0;
while total < 1000000000000 do
begin
total := 0;
day := day + 1;
calculate(topper);
end;
writeln(day);
writeln(total);
end.
Recursive functions use a stack to keep the state of the recursion.
When converting to a loop, you must actually create an explicit stack. You must push and pop elements off the stack within the loop.

Resources