Skip to content
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

Perl_do_print: stringify an SVt_IV IV/UV more efficiently #22927

Open
wants to merge 3 commits into
base: blead
Choose a base branch
from

Conversation

richardleach
Copy link
Contributor

https://stackoverflow.com/questions/79244888 points out that pp_print
is really slow at stringifying integers. It's bizarrely much faster to add
additional operations to do the stringification first.

Here are some sample timings:

1003 ms | 'for (my $i=1; $i<=10000000;$i++) { say $i }'
 630 ms | 'for (my $i=1; $i<=10000000;$i++) { say "$i" }'
 695 ms | 'for (my $i=1; $i<=10000000;$i++) { say $i."" }'
 553 ms | 'my $i = "str"; for ($i=1; $i<=10000000;$i++) { say $i }'

Note from the last timing that stringification in pp_print is not
always slow, only when the IV is stored in a SVt_IV. This traces back
to Perl_do_print which has a special path for just this case:

    if (SvTYPE(sv) == SVt_IV && SvIOK(sv)) {
        assert(!SvGMAGICAL(sv));
        if (SvIsUV(sv))
            PerlIO_printf(fp, "%" UVuf, (UV)SvUVX(sv));
        else
            PerlIO_printf(fp, "%" IVdf, (IV)SvIVX(sv));
        return !PerlIO_error(fp);
    }

(There are no code comments to say why it does this. Perhaps deliberately
preserving the type for some reason?)

PerlIO_printf calls PerlIO_vprintf, which calls Perl_vnewSVpvf, which
creates a new (effectively) temporary SV, then sends that to
Perl_sv_vcatpvfn_flags for stringification and storage of the IV.
PerlIO_vprintf then writes out the buffer from the temp SV and frees it.

All the other routes essentially call SvPV_const on $i or a PADTMP.
This invokes sv_2pv_flags and its helper, S_uiv_2buf, does the work.
No temporary SVs are created and freed in such cases.

This PR changes Perl_do_print to also use a small buffer and
S_uiv_2buf to do the stringification more efficiently, whilst still
avoiding a type upgrade, Re-measuring after this PR:

 470 ms 'for (my $i=1; $i<=10000000;$i++) { say $i }'

  • This set of changes requires a perldelta entry, and it is included.

This allows other parts of core to do integer stringification using
the same, fast function.
`Perl_do_print`'s pre-existing method for stringification of an IV within
an SVt_IV involves creating a temporary SVt_PV, using `sv_vcatpvfn_flags`
to do the stringification, then freeing the SVt_PV once the buffer has
been written out.

This is considerably slower than using `S_uiv_2buf`, the helper
function used by `sv_2pv_flags`. So this commit modifies `Perl_do_print`
to use `sv_2pv_flags`.
@richardleach richardleach changed the title Perl_do_print: stringify an SVt_IV IV more efficiently Perl_do_print: stringify an SVt_IV IV/UV more efficiently Jan 17, 2025
static const union {
char arr[200];
U16 dummy;
} int2str_table = {{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this symbol be visible in external (C/XS) code? Because at the moment it is an unprefixed identifier (no Perl_ or anything) in the global namespace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I'm not sure how best to handle it. Happy to take advice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guarding it inside a #if defined(PERL_CORE) || defined (PERL_EXT)?

else
PerlIO_printf(fp, "%" IVdf, (IV)SvIVX(sv));
return !PerlIO_error(fp);
bool happy = TRUE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is happy declared so far away from its first use?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mirroring the else branch of Perl_do_print. I kept it it the same for consistency. Happy to change both instances in a follow-up commit.

Comment on lines +2224 to +2225
if (len && (PerlIO_write(fp,ptr,len) == 0))
happy = FALSE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be

bool happy = !(len && PerlIO_write(fp, ptr, len) == 0);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was copied from the else branch of Perl_do_print. I kept it it the same for consistency. Happy to change both instances in a follow-up commit.


if (len && (PerlIO_write(fp,ptr,len) == 0))
happy = FALSE;
return happy ? !PerlIO_error(fp) : FALSE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be

return happy && !PerlIO_error(fp);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was copied from the else branch of Perl_do_print. I kept it it the same for consistency. Happy to change both instances in a follow-up commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants