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@lists.freepascal.org> schrieb am Sa., 30. Dez. 2023, 08:11:



    On Dec 29, 2023 9:50 PM, Adriaan van Os <adri...@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
_______________________________________________
fpc-pascal maillist  -  fpc-pascal@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply via email to