Sign in Join

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

Mailing lists / fpc-devel / 2020-06

Fwd: Re: An optimization suggestion for FPC

11 messages · 5 people · –

Reply by email Subscribe to this list Your subscription

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

J. Gareth Moreton

So someone reached out to me directly again asking for an FPC optimisation.  Now I want to see if this is possible to optimise and won't break something or be annoying specific.

Gareth aka. Kit

-------- Forwarded Message --------
Subject: Re: An optimization suggestion for FPC
Date: Sun, 28 Jun 2020 11:30:09 +0100
From: J. Gareth Moreton
To: Okoba

Heh, I'm honoured you came to me directly.  I'll see if I can work anything out.  A lot of it is down to where the record is stored.  If it's on the stack, an optimisation shouldn't be difficult, but if it's on the heap somewhere, then it will be a bit more difficult.  Do you have your C++ example, and which C++ compiler did you use?

Gareth aka. Kit

Okoba wrote 63 lines
Hi, It seems you are interested in optimizing FPC and I have a trouble with it and wanted to check it with you if I may. I like to have a way to pass the record variable and inc it without losing speed. I wrote a sample and ran it with FPC trunk in Win64 and the times are commented. I should say the times for Delphi for the same target is the same. And tried a C++ version and all the times are almost the same and near 250. I tried many ways including absolute, pointer, custom asm functions, custom functions with const and var params and the are almost always slower than the first loop. So is there anyway to have a custom Inc function that get he record variable and inc it without losing speed in compare to the system inc? Regards. program Project1; uses   SysUtils; type   TTest = record     P: int64;   end;   procedure Test;   var     V: TTest;     P: int64;     T: UInt64;     i, C: integer;   begin     C := 1000 * 1000 * 1000;     T := GetTickCount64;     P := 1;     for i := 1 to C do       Inc(P);     WriteLn(GetTickCount64 - T); //266     T := GetTickCount64;     V.P := 1;     for i := 1 to C do       Inc(V.P);     WriteLn(GetTickCount64 - T); //1400     T := GetTickCount64;     V.P := 1;     P := V.P;     for i := 1 to C do       Inc(P);     P := V.P;     WriteLn(GetTickCount64 - T);  //250   end; begin   Test;   ReadLn; end.
This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email

Benito van der Zander
to J. Gareth Moreton

Hi J. Gareth,

that is a really important optimization.

Especially with new record management operators. I am going to wrap almost all my variables in records, so they will be automatically initialized.

I have suggested it in the bug tracker, but it was closed, because they say the bug tracker is not for suggestions:
https://bugs.freepascal.org/view.php?id=34915

Bye,
Benito

J. Gareth Moreton wrote 98 lines
So someone reached out to me directly again asking for an FPC optimisation.  Now I want to see if this is possible to optimise and won't break something or be annoying specific. Gareth aka. Kit -------- Forwarded Message -------- Subject: Re: An optimization suggestion for FPC Date: Sun, 28 Jun 2020 11:30:09 +0100 From: J. Gareth Moreton To: Okoba Heh, I'm honoured you came to me directly.  I'll see if I can work anything out.  A lot of it is down to where the record is stored.  If it's on the stack, an optimisation shouldn't be difficult, but if it's on the heap somewhere, then it will be a bit more difficult.  Do you have your C++ example, and which C++ compiler did you use? Gareth aka. Kit On 28/06/2020 11:06, Okoba wrote: > Hi, > It seems you are interested in optimizing FPC and I have a trouble > with it and wanted to check it with you if I may. > I like to have a way to pass the record variable and inc it without > losing speed. I wrote a sample and ran it with FPC trunk in Win64 and > the times are commented. I should say the times for Delphi for the > same target is the same. And tried a C++ version and all the times > are almost the same and near 250. > I tried many ways including absolute, pointer, custom asm functions, > custom functions with const and var params and the are almost always > slower than the first loop. > So is there anyway to have a custom Inc function that get he record > variable and inc it without losing speed in compare to the system inc? > > Regards. > > > > program Project1; > > uses >   SysUtils; > > type >   TTest = record >     P: int64; >   end; > >   procedure Test; >   var >     V: TTest; >     P: int64; >     T: UInt64; >     i, C: integer; >   begin >     C := 1000 * 1000 * 1000; > >     T := GetTickCount64; >     P := 1; >     for i := 1 to C do >       Inc(P); >     WriteLn(GetTickCount64 - T); //266 > >     T := GetTickCount64; >     V.P := 1; >     for i := 1 to C do >       Inc(V.P); >     WriteLn(GetTickCount64 - T); //1400 > >     T := GetTickCount64; >     V.P := 1; >     P := V.P; >     for i := 1 to C do >       Inc(P); >     P := V.P; >     WriteLn(GetTickCount64 - T);  //250 >   end; > > begin >   Test; >   ReadLn; > end. > <https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient> Virus-free. www.avast.com <https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient> <#DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2> _______________________________________________ fpc-devel maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel

Reply by email

J. Gareth Moreton
to Benito van der Zander

Hi Benito,

I'll see what I can do in that case.  I sense the optimisation may have to be done on a per-architecture basis because of it depending on how records are stored in memory.  I'll see what I can do though.  If it has to be a per-architecture optimisation, I'll only be able to do it for i386, x86_64 and whatever ARM processor is in my Raspberry Pi!

Gareth aka. Kit

Benito van der Zander wrote 117 lines
Hi J. Gareth, that is a really important optimization. Especially with new record management operators. I am going to wrap almost all my variables in records, so they will be automatically initialized. I have suggested it in the bug tracker, but it was closed, because they say the bug tracker is not for suggestions: https://bugs.freepascal.org/view.php?id=34915 Bye, Benito On 28.06.20 12:31, J. Gareth Moreton wrote: > > So someone reached out to me directly again asking for an FPC > optimisation.  Now I want to see if this is possible to optimise and > won't break something or be annoying specific. > > Gareth aka. Kit > > -------- Forwarded Message -------- > Subject: Re: An optimization suggestion for FPC > Date: Sun, 28 Jun 2020 11:30:09 +0100 > From: J. Gareth Moreton > To: Okoba > > > > Heh, I'm honoured you came to me directly.  I'll see if I can work > anything out.  A lot of it is down to where the record is stored.  If > it's on the stack, an optimisation shouldn't be difficult, but if > it's on the heap somewhere, then it will be a bit more difficult.  Do > you have your C++ example, and which C++ compiler did you use? > > Gareth aka. Kit > > On 28/06/2020 11:06, Okoba wrote: >> Hi, >> It seems you are interested in optimizing FPC and I have a trouble >> with it and wanted to check it with you if I may. >> I like to have a way to pass the record variable and inc it without >> losing speed. I wrote a sample and ran it with FPC trunk in Win64 >> and the times are commented. I should say the times for Delphi for >> the same target is the same. And tried a C++ version and all the >> times are almost the same and near 250. >> I tried many ways including absolute, pointer, custom asm functions, >> custom functions with const and var params and the are almost always >> slower than the first loop. >> So is there anyway to have a custom Inc function that get he record >> variable and inc it without losing speed in compare to the system inc? >> >> Regards. >> >> >> >> program Project1; >> >> uses >>   SysUtils; >> >> type >>   TTest = record >>     P: int64; >>   end; >> >>   procedure Test; >>   var >>     V: TTest; >>     P: int64; >>     T: UInt64; >>     i, C: integer; >>   begin >>     C := 1000 * 1000 * 1000; >> >>     T := GetTickCount64; >>     P := 1; >>     for i := 1 to C do >>       Inc(P); >>     WriteLn(GetTickCount64 - T); //266 >> >>     T := GetTickCount64; >>     V.P := 1; >>     for i := 1 to C do >>       Inc(V.P); >>     WriteLn(GetTickCount64 - T); //1400 >> >>     T := GetTickCount64; >>     V.P := 1; >>     P := V.P; >>     for i := 1 to C do >>       Inc(P); >>     P := V.P; >>     WriteLn(GetTickCount64 - T);  //250 >>   end; >> >> begin >>   Test; >>   ReadLn; >> end. >> > > <https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient> > Virus-free. www.avast.com > <https://www.avast.com/sig-email?utm_medium=email&utm_source=link&utm_campaign=sig-email&utm_content=emailclient> > > > <#DAB4FAD8-2DD7-40BB-A1B8-4E2AA1F9FDF2> > > _______________________________________________ > fpc-devel maillist > https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel _______________________________________________ fpc-devel maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email

Jonas Maebe
to J. Gareth Moreton: So someone reached out to me directly again asking for an FPC optimisation.  Now I want…

[accidentally only sent to Gareth initially]

J. Gareth Moreton wrote
So someone reached out to me directly again asking for an FPC optimisation.  Now I want to see if this is possible to optimise and won't break something or be annoying specific.

The general optimisation that would handle this is promoting individual record members into standalone variables when possible. FPC currently has no support at all for this.

An optimisation that's a bit less general (although orthogonal in some cases, namely when you don't need to access individual members), is keeping records as a whole in a register. FPC already has support for this, see tstoreddef.is_intregable and tabstractvarsym.setregable.

It does not get triggered here on x86-64 because of another involved method: the {$if} at the end of tabstractvarsym.is_regvar. That code prevents records from being kept in registers if they are written to on all architectures except for PowerPC and PowerPC64.

The reason for this is that other supported architectures lack instructions to efficiently extract and insert bitfields from/into integer registers (although perhaps some of the newer x86-64 include them as part of an extension; and I think AArch64 and certain MIPS subarchs could also support it efficiently). This means that to perform an operation on a field of a record kept in a register, you have to do the following in the general case:
1) extract the field. On generic x86, that would be a move to a temporary register, then possibly a shift, and then possibly an "and".
2) perform the operation
3) possibly shift back the value to the corect position, clear it in the original register (mask its position with 0), and then "or" the result to insert it again

In this case, just loading a value from memory (probably L1 cache, since register variables are only used locally within a single routine), performing the operation, and storing it back, is quite likely to be faster, and definitely results in much smaller code.

However, as you've undoubtedly realised, in this case none of that shifting/masking would come into play, since the record only contains a single field. So you could definitely add an exception for that case for all architectures. We even have the perfect helper method for that in the mean time: tabstractrecordsymtable.has_single_field()

Jonas

PS: that person also asked the same question on the forum (https://forum.lazarus.freepascal.org/index.php?topic=50364)

PS2: the case Benito mentions is a different thing again. Managed records can never be kept *only* in a register, because they need initialisation and finalisation, which requires them to be in memory. Caching individual fields of those locally in a register (while the record itself remains in memory) would definitely require the general optimisation I mentioned in the first paragraph.

Reply by email

J. Gareth Moreton
to Jonas Maebe

Thanks Jonas.  I'll see what I can put together.  A record with a single field is a bit of a special case, but one I'll keep in mind.  More than anything I'll have to study the disassembly to see what's happening, and if things are faster with primitive types simply because they're register variables (which are always faster than stack variables even on L1) or due to something else.

Gareth aka. Kit

Jonas Maebe wrote 61 lines
[accidentally only sent to Gareth initially] On 28/06/2020 12:31, J. Gareth Moreton wrote: > So someone reached out to me directly again asking for an FPC > optimisation.  Now I want to see if this is possible to optimise and > won't break something or be annoying specific. The general optimisation that would handle this is promoting individual record members into standalone variables when possible. FPC currently has no support at all for this. An optimisation that's a bit less general (although orthogonal in some cases, namely when you don't need to access individual members), is keeping records as a whole in a register. FPC already has support for this, see tstoreddef.is_intregable and tabstractvarsym.setregable. It does not get triggered here on x86-64 because of another involved method: the {$if} at the end of tabstractvarsym.is_regvar. That code prevents records from being kept in registers if they are written to on all architectures except for PowerPC and PowerPC64. The reason for this is that other supported architectures lack instructions to efficiently extract and insert bitfields from/into integer registers (although perhaps some of the newer x86-64 include them as part of an extension; and I think AArch64 and certain MIPS subarchs could also support it efficiently). This means that to perform an operation on a field of a record kept in a register, you have to do the following in the general case: 1) extract the field. On generic x86, that would be a move to a temporary register, then possibly a shift, and then possibly an "and". 2) perform the operation 3) possibly shift back the value to the corect position, clear it in the original register (mask its position with 0), and then "or" the result to insert it again In this case, just loading a value from memory (probably L1 cache, since register variables are only used locally within a single routine), performing the operation, and storing it back, is quite likely to be faster, and definitely results in much smaller code. However, as you've undoubtedly realised, in this case none of that shifting/masking would come into play, since the record only contains a single field. So you could definitely add an exception for that case for all architectures. We even have the perfect helper method for that in the mean time: tabstractrecordsymtable.has_single_field() Jonas PS: that person also asked the same question on the forum (https://forum.lazarus.freepascal.org/index.php?topic=50364) PS2: the case Benito mentions is a different thing again. Managed records can never be kept *only* in a register, because they need initialisation and finalisation, which requires them to be in memory. Caching individual fields of those locally in a register (while the record itself remains in memory) would definitely require the general optimisation I mentioned in the first paragraph. _______________________________________________ fpc-devel maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email

Jonas Maebe
to J. Gareth Moreton
J. Gareth Moreton wrote 6 lines
Thanks Jonas.  I'll see what I can put together.  A record with a single field is a bit of a special case, but one I'll keep in mind.  More than anything I'll have to study the disassembly to see what's happening, and if things are faster with primitive types simply because they're register variables (which are always faster than stack variables even on L1) or due to something else.

This issue is not register versus memory, it's "register + a bunch of masking and inserting" versus memory, plus the code size overhead (which will probably not have any noticeable effects in a small benchmark).

Jonas

Reply by email

J. Gareth Moreton
to Jonas Maebe

Hi Jonas,

I've made a patch that hopefully should optimise the situation where the record type contains only one field, following the tips you gave me regarding the "has_single_field()" method.

https://bugs.freepascal.org/view.php?id=37343

I haven't been able to test it as rigorously as I would have liked.  I'm having some difficulty compiling projects under Lazarus with the trunk since it tries to recompile windows.ppu due to a checksum mismatch, and immediately fails upon not being able to find a file.  I might have some configuation problems, because when I try to compile the test program directly from the command line, it just hangs when I run it (even without the patch applied), and there's nothing in the code that should cause an infinite loop.  I can, however, analyse the disassembly and see that the optimisation has indeed been applied correctly.

Gareth aka. Kit

Jonas Maebe wrote 17 lines
On 28/06/2020 14:18, J. Gareth Moreton wrote: > Thanks Jonas.  I'll see what I can put together.  A record with a single > field is a bit of a special case, but one I'll keep in mind.  More than > anything I'll have to study the disassembly to see what's happening, and > if things are faster with primitive types simply because they're > register variables (which are always faster than stack variables even on > L1) or due to something else. This issue is not register versus memory, it's "register + a bunch of masking and inserting" versus memory, plus the code size overhead (which will probably not have any noticeable effects in a small benchmark). Jonas _______________________________________________ fpc-devel maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email

Sven Barth
to J. Gareth Moreton

J. Gareth Moreton schrieb am Mo., 13. Juli
2020, 12:23:

Show quoted text 10 lines
I haven't been able to test it as rigorously as I would have liked. I'm having some difficulty compiling projects under Lazarus with the trunk since it tries to recompile windows.ppu due to a checksum mismatch, and immediately fails upon not being able to find a file. I might have some configuation problems, because when I try to compile the test program directly from the command line, it just hangs when I run it (even without the patch applied), and there's nothing in the code that should cause an infinite loop. I can, however, analyse the disassembly and see that the optimisation has indeed been applied correctly.

That sounds like you have some problem in your setup, e.g. when the compiler finds a Windows.pp file somewhere where there shouldn't be one. You could try to pass -vu and/or -vt to see which units it is really using.

Regards,
Sven

Reply by email

J. Gareth Moreton
to Sven Barth

I'm still having problems.  It's not letting me rebuild Lazarus for one thing:

jcfidemain.pas(41,3) Fatal: Cannot find LazIDEIntf used by JcfIdeMain, incompatible
ppu=c:\lazarus\components\ideintf\units\x86_64-win64\win32\lazideintf.ppu, package IDEIntf

I even manually deleted the ppu file in question before opening Lazarus to use the internal build wizard.  I know I'm probably missing something obvious, but I'm not sure what.  I'm wondering if I should just completely delete lazarus and build from a fresh install, and hope I don't lose the ability to edit my projects!

Granted, having multiple versions of Lazarus installed on my system probably doesn't help (I tend to keep a stable release version installed for non-compiler work).

Gareth aka. Kit

Sven Barth wrote 32 lines
J. Gareth Moreton < > schrieb am Mo., 13. Juli 2020, 12:23: I haven't been able to test it as rigorously as I would have liked.  I'm having some difficulty compiling projects under Lazarus with the trunk since it tries to recompile windows.ppu due to a checksum mismatch, and immediately fails upon not being able to find a file.  I might have some configuation problems, because when I try to compile the test program directly from the command line, it just hangs when I run it (even without the patch applied), and there's nothing in the code that should cause an infinite loop.  I can, however, analyse the disassembly and see that the optimisation has indeed been applied correctly. That sounds like you have some problem in your setup, e.g. when the compiler finds a Windows.pp file somewhere where there shouldn't be one. You could try to pass -vu and/or -vt to see which units it is really using. Regards, Sven _______________________________________________ fpc-devel maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email

Mattias Gaertner
to J. Gareth Moreton
Show quoted text 9 lines
I'm still having problems.  It's not letting me rebuild Lazarus for one thing: jcfidemain.pas(41,3) Fatal: Cannot find LazIDEIntf used by JcfIdeMain, incompatible ppu=c:\lazarus\components\ideintf\units\x86_64-win64\win32\lazideintf.ppu, package IDEIntf I even manually deleted the ppu file in question

Did you try deleting all ppu files using 'make distclean'?

Show quoted text 9 lines
before opening Lazarus to use the internal build wizard.  I know I'm probably missing something obvious, but I'm not sure what.  I'm wondering if I should just completely delete lazarus and build from a fresh install, and hope I don't lose the ability to edit my projects! Granted, having multiple versions of Lazarus installed on my system probably doesn't help (I tend to keep a stable release version installed for non-compiler work).

Make sure Lazarus uses the right lpk files.
For example Package / Install Package. Click on each package to see the path.

And finally as Sven already suggested:
Compile with -vut and check for checksum changed and where fpc searches and what it finds.

Mattias

Reply by email

J. Gareth Moreton
to Mattias Gaertner

I fixed my problems for now.  I ended up deleting or uninstalling all copies of Lazarus and FPC, installed FPC 3.2.0 and built fresh trunk copies of Lazarus and FPC.  That solved the issues.  I think I was having a conflict with the multiple versions I had installed.

Now back to investigating bugs!

Gareth aka. Kit

Mattias Gaertner wrote 36 lines
On Mon, 13 Jul 2020 21:17:39 +0100 "J. Gareth Moreton" wrote: > I'm still having problems.  It's not letting me rebuild Lazarus for > one thing: > > jcfidemain.pas(41,3) Fatal: Cannot find LazIDEIntf used by > JcfIdeMain, incompatible > ppu=c:\lazarus\components\ideintf\units\x86_64-win64\win32\lazideintf.ppu, > package IDEIntf > > I even manually deleted the ppu file in question Did you try deleting all ppu files using 'make distclean'? > before opening > Lazarus to use the internal build wizard.  I know I'm probably > missing something obvious, but I'm not sure what.  I'm wondering if I > should just completely delete lazarus and build from a fresh install, > and hope I don't lose the ability to edit my projects! > > Granted, having multiple versions of Lazarus installed on my system > probably doesn't help (I tend to keep a stable release version > installed for non-compiler work). Make sure Lazarus uses the right lpk files. For example Package / Install Package. Click on each package to see the path. And finally as Sven already suggested: Compile with -vut and check for checksum changed and where fpc searches and what it finds. Mattias _______________________________________________ fpc-devel maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email