[fpc-pascal] How to avoid Copy
Sven Barth
pascaldragon at googlemail.com
Wed Jan 3 07:47:30 CET 2024
Am 31.12.2023 um 04:11 schrieb Amir--- via fpc-pascal:
>
> On 12/30/23 00:20, Sven Barth via fpc-pascal wrote:
>> Amir via fpc-pascal <fpc-pascal at lists.freepascal.org> schrieb am Sa.,
>> 30. Dez. 2023, 08:11:
>>
>>
>>
>> On Dec 29, 2023 9:50 PM, Adriaan van Os <adriaan at adriaan.biz> wrote:
>>
>> Amir--- via fpc-pascal wrote:
>> > Hi all,
>> >
>> > I have a List of record, where the record has a WideString
>> field.
>> > I have some code like the following:
>> >
>> > function check(constref v: TMyRecord; data:
>> TListOfMyRecord): Boolean;
>> > var
>> > r: TMyRecord;
>> >
>> > begin
>> > Result := False;
>> > for r in data do
>> > if r.State = v.State then
>> > Exit(True);
>> > end;
>> >
>> > I call this method a lot and the CPU profiling shows a lot
>> of cpu time
>> > spent on "fpc_copy_proc" (which I assume is doing the deep
>> copy on
>> > records) from "TCustomListEnumerator.GetCurrent".
>> > I considered other alternatives like using enumerators but
>> they all need
>> > a to return a record (and hence copying the widestring field).
>> > I can think of two solutions to get rid of the wasting(!)
>> so much time
>> > on "fpc_copy_proc":
>> > 1) Changing the TMyRecord to TMyClass. But then I need to
>> Create and
>> > Free a "lot" of objects.
>> > 2) Update TListOfMyRecord to TListOfPointerToMyRecord. This
>> requires a
>> > "lot" of memory allocation/fragmentation.
>> >
>> > Is there a better solution?
>>
>> Pass the data parameter by reference.
>>
>> This means I need to have a ListOfMyRecord and a
>> ListOfConstRefMyRecord, right?
>>
>>
>> No, that's not a thing.
>>
>> You simply need to declare your "data" parameter as "constref" or
>> "const" as well, just like your "v" parameter.
> Have a look at this piece of code (The complete code is attached):
> type
> TMyRecord = record
> Str: AnsiString;
> Index: Integer;
>
> end;
> PMyRecord = ^TMyRecord;
>
> TMyList = specialize TList<TMyRecord>;
> TMyPtrList = specialize TList<PMyRecord>;
>
> function Check1(const MyList: TMyList): Integer;
> var
> data: TMyRecord;
>
> begin
> Result := 0;
> for data in MyList do
> if data.Index mod 100 = 0 then
> Inc(Result);
>
> end;
>
> function Check2(MyList: TMyList): Integer;
> var
> data: TMyRecord;
>
> begin
> Result := 0;
> for data in MyList do
> if data.Index mod 100 = 0 then
> Inc(Result);
>
> end;
>
> function Check3(MyPtrList: TMyPtrList): Integer;
> var
> data: PMyRecord;
>
> begin
> Result := 0;
> for data in MyPtrList do
> if data^.Index mod 100 = 0 then
> Inc(Result);
>
> end;
>
>
> I compiled the code with `fpc -O3 -Sd -gv -g -gl ` and ran `valgrind`
> on it (the output is attached). It does not look like there is a big
> difference between the Check1 and Check2 but Check3 is about 20 times
> faster than the other two.
For a class type there isn't much difference between being passed as
"const" or not. It's mainly records and managed types this affects.
> I believe the issue could be resolved if we make
> "TCustomListWithPointers.GetPtrEnumerator" a public method. Then, one
> can implement the following function:
>
> function Check4(MyList: TMyList): Integer;
> ....
> it := MyList.GetPreEnumerator;
> while it.MoveNext do
> begin
> if it.Current^.Index mod 100 = 0 then
> ....
> end;
You simply need to inherit from the list class so that you can make the
function public. And with a little trick you can also use it inside a
for-in-loop:
=== code begin ===
program tlistitr;
{$mode objfpc}{$H+}
{$modeswitch advancedrecords}
uses
Generics.Collections;
type
TRec = record
a, b, c: Int64;
constructor Create(aArg1, aArg2, aArg3: Int64);
end;
{ this way the GetPtrEnumerator function is available; you could also
use a class helper }
TMyList = class(specialize TList<TRec>)
public
function GetPtrEnumerator: specialize TEnumerator<PT>;
end;
constructor TRec.Create(aArg1, aArg2, aArg3: Int64);
begin
a := aArg1;
b := aArg2;
c := aArg3;
end;
function TMyList.GetPtrEnumerator: specialize TEnumerator<PT>;
begin
Result := inherited GetPtrEnumerator();
end;
{ with this you can simply do "for PtrTypeVar in List.GetPtrEnumerator do",
though this *might* not work in mode Delphi... }
operator Enumerator(aEnum: specialize TEnumerator<TMyList.PT>):
specialize TEnumerator<TMyList.PT>;
begin
Result := aEnum;
end;
var
l: TMyList;
r: TRec;
p: TMyList.PT;
begin
l := TMyList.Create;
l.Add(TRec.Create(1, 2, 3));
l.Add(TRec.Create(9, 8, 7));
for p in l.GetPtrEnumerator do begin
Writeln(p^.a, ' ', p^.b, ' ', p^.c);
end;
end.
=== code end ===
Regards,
Sven
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.freepascal.org/pipermail/fpc-pascal/attachments/20240103/228216b6/attachment-0001.htm>
More information about the fpc-pascal
mailing list