[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