Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2023-12

How to avoid Copy

18 messages · 8 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Unknown author

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?

Best,
Amir

Reply by email

Adriaan van Os
to Unknown author
Amir--- wrote 29 lines
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.

Regards,

Adriaan van Os

Reply by email

Michael Van Canneyt
to Amir
Amir wrote 38 lines
On Dec 29, 2023 9:50 PM, Adriaan van Os 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?

It means you use
constref data: TListOfMyRecord
or
var data: TListOfMyRecord

And try to avoid "for r in data" because it will create a copy for each element in the list.

I don't know how you defined TListOfMyRecord, but if it is a generic TList<TMyRecord>, better change it to a TArray<TMyRecord> or Array of TMyRecord, in which case you can use a pointer to the consecutive records in the array. Generics are convenient, but slow.

Michael.

Reply by email

Sven Barth
to Amir:

Amir via fpc-pascal schrieb am Sa., 30.
Dez. 2023, 08:11:

Show quoted text 40 lines
On Dec 29, 2023 9:50 PM, Adriaan van Os 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.

Note: prefer the use of "const" instead of "constref" unless you really need a reference, because the compiler will then pick the optimal way to pass the value (e.g. small ones will be passed by register instead of as an address).

Regards,
Sven

Reply by email

Unknown author
to Sven Barth
Sven Barth wrote 56 lines
Amir via fpc-pascal schrieb am Sa., 30. Dez. 2023, 08:11: On Dec 29, 2023 9:50 PM, Adriaan van Os 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.
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;

Reply by email

Marco van de Voort
to Unknown author

Op 31/12/2023 om 04:11 schreef Amir--- via fpc-pascal:

Show quoted text 9 lines
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. I believe the issue could be resolved if we make "TCustomListWithPointers.GetPtrEnumerator" a public method. Then, one can implement the following function:

 I also do this (an enumerator using a pointer type) in one of my container types. It also makes assignment in the for in possible.  Note though that it is not necessarily needed to switch the whole collection to use a pointer, defining a separate iterator (e.g. collection.pointeriterator) that returns an iterator instantiated for ^T.   You can then select the iterator by using for element in collection.iterator do.

The rtl-generics collections already have multiple iterators since for maps it is possible to iterate over the keys, the values or TPair<TKey,TValue)

Reply by email

Unknown author
to Marco van de Voort
Marco van de Voort wrote 18 lines
Op 31/12/2023 om 04:11 schreef Amir--- via fpc-pascal: > > 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. > I believe the issue could be resolved if we make > "TCustomListWithPointers.GetPtrEnumerator" a public method. Then, one > can implement the following function: >  I also do this (an enumerator using a pointer type) in one of my container types. It also makes assignment in the for in possible. Note though that it is not necessarily needed to switch the whole collection to use a pointer, defining a separate iterator (e.g. collection.pointeriterator) that returns an iterator instantiated for ^T.   You can then select the iterator by using for element in collection.iterator do.

Understand (and this is what I ended-up doing). I guess my question is that why we do not to have a PointerIterator in TList class.

Amir

Reply by email

Michael Van Canneyt
to Unknown author
Amir--- wrote 23 lines
On 12/31/23 02:46, Marco van de Voort via fpc-pascal wrote: > > Op 31/12/2023 om 04:11 schreef Amir--- via fpc-pascal: >> >> 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. >> I believe the issue could be resolved if we make >> "TCustomListWithPointers.GetPtrEnumerator" a public method. Then, one >> can implement the following function: >> >  I also do this (an enumerator using a pointer type) in one of my > container types. It also makes assignment in the for in possible. Note > though that it is not necessarily needed to switch the whole > collection to use a pointer, defining a separate iterator (e.g. > collection.pointeriterator) that returns an iterator instantiated for > ^T.   You can then select the iterator by using for element in > collection.iterator do. Understand (and this is what I ended-up doing). I guess my question is that why we do not to have a PointerIterator in TList class.

Because no-one thought to implement it, or no-one saw the need.

Michael.

Reply by email

Hairy Pixels
to Michael Van Canneyt: It means you use constref data: TListOfMyRecord or var data: TListOfMyRecord And try to…
On Dec 30, 2023, at 3:18 PM, Michael Van Canneyt via fpc-pascal wrote: And try to avoid "for r in data" because it will create a copy for each element in the list.

This seems more like bad compiler design than anything. From the users perspective a for loop is a read-only operation and shouldn't be copying anything. I know it's how the enumerator thing is done but that may have been a bad design from the start or needs some extra optimization somewhere.

For OP I would just kill the for-in loop in favor of a normal for-do loop and be done with the problem.

Regards,
Ryan Joseph

Reply by email

Michael Van Canneyt
to Hairy Pixels
Hairy Pixels wrote 12 lines
> On Dec 30, 2023, at 3:18 PM, Michael Van Canneyt via fpc-pascal wrote: > > And try to avoid "for r in data" because it will create a copy for each element > in the list. This seems more like bad compiler design than anything. From the users perspective a for loop is a read-only operation and shouldn't be copying anything. I know it's how the enumerator thing is done but that may have been a bad design from the start or needs some extra optimization somewhere.

You can't optimize that. As said, a generic is convenient but slow.

For OP I would just kill the for-in loop in favor of a normal for-do loop and be done with the problem.

That will not help, since he is using a TList<TMyRecord>, it cannot work without copy: any read implies a copy. Unless you use a pointer as Marco and I suggested.

Michael.

Reply by email

Hairy Pixels
to Michael Van Canneyt
On Jan 1, 2024, at 3:50 PM, Michael Van Canneyt wrote: You can't optimize that. As said, a generic is convenient but slow.

I don't know about that. Like was mentioned the enumerator needs to return a pointer, preferable without ^ so it feels like a record and only use that in the for-in scope. You can kind of do that yourself but it's cumbersome to maintain and missing from the RTL (maybe for this reason).

Regards,
Ryan Joseph

Reply by email

Wayne Sherman
to Hairy Pixels
Show quoted text 7 lines
> On Jan 1, 2024, at 3:50 PM, Michael Van Canneyt wrote: > You can't optimize that. As said, a generic is convenient but slow. I don't know about that. Like was mentioned the enumerator needs to return a pointer, preferable without ^ so it feels like a record and only use that in the for-in scope. You can kind of do that yourself but it's cumbersome to maintain and missing from the RTL (maybe for this reason).

Modern Pascal compilers already do this for certain types and pass by reference parameters. For example, AnsiString variables and variables of type class are handled internally as pointers, but manipulated opaquely without explicit pointer notation. A record passed as a var or constref parameter is internally a pointer, but does not require the code writer to handle it explicitly as such.

Reply by email

Sven Barth
to Wayne Sherman
Wayne Sherman wrote 13 lines
On Mon, Jan 1, 2024 at 6:14 AM Hairy Pixels wrote: >> On Jan 1, 2024, at 3:50 PM, Michael Van Canneyt wrote: >> You can't optimize that. As said, a generic is convenient but slow. > I don't know about that. Like was mentioned the enumerator needs to return a > pointer, preferable without ^ so it feels like a record and only use that in the for-in > scope. You can kind of do that yourself but it's cumbersome to maintain and > missing from the RTL (maybe for this reason). Modern Pascal compilers already do this for certain types and pass by reference parameters. For example, AnsiString variables and variables of type class are handled internally as pointers, but manipulated opaquely without explicit pointer notation. A record passed as a var or constref parameter is internally a pointer, but does not require the code writer to handle it explicitly as such.

That kind of concept only exists in Pascal for parameters, but not for variables or result values as would be needed for this.

Regards,
Sven

Reply by email

Sven Barth
to Unknown author: Have a look at this piece of code (The complete code is attached): type   TMyRecord =…
Amir--- wrote 111 lines
On 12/30/23 00:20, Sven Barth via fpc-pascal wrote: > Amir via fpc-pascal schrieb am Sa., > 30. Dez. 2023, 08:11: > > > > On Dec 29, 2023 9:50 PM, Adriaan van Os 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.

Show quoted text 12 lines
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

Reply by email

Unknown author
to Sven Barth

Yeap! That is actually what I posted here (Feature Request) <https://gitlab.com/freepascal.org/fpc/source/-/issues/40578>.

Show quoted text 69 lines
 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 _______________________________________________ fpc-pascal maillist https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply by email

Unknown author
to Sven Barth

Thanks!

Sven Barth wrote 17 lines
Amir--- via fpc-pascal schrieb am Mi., 3. Jan. 2024, 07:53: Yeap! That is actually what I posted here (Feature Request) <https://gitlab.com/freepascal.org/fpc/source/-/issues/40578>. My example allows you to access it right now with the existing FPC release. Regards, Sven _______________________________________________ fpc-pascal maillist https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply by email