-
Notifications
You must be signed in to change notification settings - Fork 555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make newSV_type() an inline function #19414
Conversation
Note: xenu looked at preventing inlining when the desired type is not know
|
cd77597
to
6f86b0e
Compare
6f86b0e
to
562f242
Compare
On Sun, 13 Feb 2022 at 23:54, Richard Leach ***@***.***> wrote:
Note: This is an alternative to #19381
<#19381>, following feedback from @xenu
<https://github.com/xenu>.
When a new SV is created and upgraded to a type known at compile time,
using the general-purpose upgrade function (sv_upgrade) is clunky.
Specifically, while uprooting a SV head is lightweight (assuming there are
unused SVs), sv_upgrade is too big to be inlined, contains many branches
that can logically be resolved at compile time for known start & end types,
and the lookup of the correct body_details struct may add CPU cycles.
This PR:
- Adds a new file - *sv_inline.h*, into which are moved many of the
definitions
and structures from *sv.c*. This seemed necessary because of the spread
of type definitions across existing header files.
- Converts newSV_type into an inline function and adds to it the logic
from
sv_upgrade necessary to upgrade a SVt_NULL.
Building on that, the commits in this PR:
- Modify existing calls to newSV(sv) followed by an sv_upgrade(sv,
type) to
just use newSV_type, so that they also benefit.
- Replaces calls to newSV(0) with newSV_type(SVt_NULL)
- Add a new inline function, newSV_type_mortal, to address the absence
of
an efficient way to make a new non-SVt_NULL mortal SV.
With gcc version 10.2.1 on Debian Linux, the resulting perl binary was 25k
larger than blead. (The main commit accounts for almost all of this.)
I used the following trivial benchmark as a gauge of the performance
difference, finding the patched version to be about 30% faster:
perl -e '$str="A"x64; for (0 .. 1_000_000) { @svs = split //, $str }'
dumbbench concurs:
sv_upgrade_fresh1:~/git_tree/perl$ dumbbench -- ./perl -Ilib -e
'$str="A"x64; for (0 .. 1_000_000) { @svs = split //, $str }'
cmd: Ran 21 iterations (1 outliers).
cmd: Rounded run time per iteration: 3.0000e+00 +/- 5.6e-03 (0.2%)
blead:~/git_tree/perl2$ dumbbench -- ./perl -Ilib -e '$str="A"x64; for (0
.. 1_000_000) { @svs = split //, $str }'
cmd: Ran 24 iterations (4 outliers).
cmd: Rounded run time per iteration: 4.0168e+00 +/- 2.9e-03 (0.1%)
Nice.
cheers,
Yves
…--
perl -Mre=debug -e "/just|another|perl|hacker/"
|
1b996aa
to
83babae
Compare
b33fd2d
to
8a82591
Compare
Two small issues remaining:
Otherwise LGTM. |
8a82591
to
d310d10
Compare
When a new SV is created and upgraded to a type known at compile time, uprooting a SV head and then using the general-purpose upgrade function (sv_upgrade) is clunky. Specifically, while uprooting a SV head is lightweight (assuming there are unused SVs), sv_upgrade is too big to be inlined, contains many branches that can logically be resolved at compile time for known start & end types, and the lookup of the correct body_details struct may add CPU cycles. This commit tries to address that by making newSV_type an inline function and including only the parts of sv_upgrade needed to upgrade a SVt_NULL. When the destination type is known at compile time, a decent compiler will inline a call to newSV_type and use the type information to throw away all the irrelevant parts of the sv_upgrade logic. Because of the spread of type definitions across header files, it did not seem possible to make the necessary changed inside sv.h, and so a new header file (sv_inline.h) was created. For the inlined function to work outside of sv.c, many definitions from that file were moved to sv_inline.h. Finally, in order to also benefit from this change, existing code in sv.c that does things like this: SV* sv; new_SV(sv); sv_upgrade(sv, SVt_PV) has been modified to read something like: SV* sv = newSV_type(SVt_PV);
When a function outside of sv.c creates a SV via newSV(0): * There is a call to Perl_newSV * A SV head is uprooted and its flags set * A runtime check is made to effectively see if 0 > 0 * The new SV* is returned Replacing newSV(0) with newSV_type(SVt_NULL) should be more efficient, because (assuming there are SV heads to uproot), the only step is: * A SV head is uprooted and its flags set
There's no efficient way to create a mortal SV of any type other than SVt_NULL (via sv_newmortal). The options are either to do: * SV* sv = sv_newmortal; sv_upgrade(sv, SVt_SOMETYPE); but sv_upgrade is needlessly inefficient on new SVs. * SV* sv = sv_2mortal(newSV_type(SVt_SOMETYPE) but this will perform runtime checks to see if (sv) and if (SvIMMORTAL(sv), and for a new SV we know that those answers will always be yes and no. This commit adds a new inline function which is basically a mortalizing wrapper around the now-inlined newSV_type.
d310d10
to
f327c5a
Compare
Thanks, I've addressed those two points. (Odd how the waas-commented-out code didn't have an apparent effect, but I can always look at why separately.) |
Related to #22667 I believe this commit needs to be reverted at this point. I compiled blead perl with GCC 8.3.0 i686 mode. It uses -Os optimization flag. 18 unique copies of struct bodies_by_details inside perl541.dll. The byte values inside each struct field in the array bodies_by_details are NOT considered as hoistable or inlineable by GCC even though they are declared as In every .o, unless all callers of static inline Perl_newSV_type(), pass the same sv_type value, Perl_newSV_type() will not inline. There are 16 copies of full sized Perl_newSV_type(). Full sized mean that the switch that handles all 17 SV types is there. In 17 locations total, inside perl541.dll Perl_newSV_type() was totally inlined into its caller, or a not-inlined "one SV type" fast efficient version of Perl_newSV_type() was created. Unless reproducible benchmarks are demonstrated, or disassembly/assembler deltas can be shown by someone, that justify this commit, or it is #ifdef-ed to exactly to one OS and one verified correct behavior CC with a hard coded version/build number of that CC, this needs to be reverted. It is not a MSVC only problem problem. Its an every CC problem including GCC. Therefore I recommend reverting it. |
Since GCC does not believe
Even in fast efficient "one SV type" versions of There is too much to fix, to get the original authors intent of this branch, after "fixing" this branch, there will be no source code lines left from this branch basically. This merge added nothing of value or no code that can be kept. C/C++ language is not NodeJS, Java, or CLR/.NET. And C compilers, all of them, won't optimize as if the perl interpreter is a just another javascript file. |
Who is building perl with gcc and -Os and what's their use case? As noted earlier in the PR, this gives a good performance boost to people using the default optimization flags (-O2, no -Os). This, I believe, includes many of the Linux & BSD distro builds. If the majority of non-Windows users are getting the benefit of I certainly DO agree that there's room for improvement around handling of |
Note: This is an alternative to #19381, following feedback from @xenu.
When a new SV is created and upgraded to a type known at compile time,
using the general-purpose upgrade function (
sv_upgrade
) is clunky.Specifically, while uprooting a SV head is lightweight (assuming there are
unused SVs),
sv_upgrade
is too big to be inlined, contains many branchesthat can logically be resolved at compile time for known start & end types,
and the lookup of the correct body_details struct may add CPU cycles.
This PR:
and structures from sv.c. This seemed necessary because of the spread
of type definitions across existing header files.
newSV_type
into an inline function and adds to it the logic fromsv_upgrade
necessary to upgrade a SVt_NULL.Building on that, the commits in this PR:
newSV(sv)
followed by ansv_upgrade(sv, type)
tojust use
newSV_type
, so that they also benefit.newSV(0)
withnewSV_type(SVt_NULL)
an efficient way to make a new non-SVt_NULL mortal SV.
With gcc version 10.2.1 on Debian Linux, the resulting perl binary was 25k
larger than blead. (The main commit accounts for almost all of this.)
I used the following trivial benchmark as a gauge of the performance
difference, finding the patched version to be about 30% faster:
perl -e '$str="A"x64; for (0 .. 1_000_000) { @svs = split //, $str }'
perf showed numbers in this region for blead:
with sv_upgrade taking 25% of the run time:
perf showed numbers in this region for patched:
and other functions coming to the fore:
Note: This might be the best-case benchmark, as it is pretty much all about SV creation
and destruction, with little overhead from op dispatch or other functions.
This commit has left the big chunk of body commentary in sv.c somewhat adrift of
e.g.
the bodies_by_type
table. I don't know how best to tidy that up. Hoping for somefeedback and suggestions!