Skip to content

Add OP_REF_CMP to optimize (ref $x eq/ne 'SCALAR') patterns#24459

Open
richardleach wants to merge 3 commits into
Perl:bleadfrom
richardleach:pp_ref_seq_coretype
Open

Add OP_REF_CMP to optimize (ref $x eq/ne 'SCALAR') patterns#24459
richardleach wants to merge 3 commits into
Perl:bleadfrom
richardleach:pp_ref_seq_coretype

Conversation

@richardleach

Copy link
Copy Markdown
Contributor

Finding value of ref, comparing it to a specific one of Perl's builtin types, and doing something with that boolean result is currently a fair bit of work.

if (ref $x eq 'SCALAR) { # do something }

or

if (ref $x ne 'SCALAR) { # do something }

The type of $x has to be determined and the string value for it pushed onto the stack, the value to compare it to is also pushed, a string comparison is then performed, with &PL_sv_yes or &PL_sv_no put on the stack to be consumed by a LOGOP in order to determine the control flow.

The optree looks something like this:

4     <;> nextstate(main 2 -e:1) v:{ ->5
-     <1> null vK/1 ->d
9        <|> and(other->a) vK/1 ->d
8           <2> seq sKP/2 ->9
6              <1> ref[t2] sK/1 ->7
5                 <0> padsv[$x:1,2] s ->6
7              <$> const[PV "SCALAR"] s/BARE ->8

This commit instead adds an OP_REF_SEQNE optimization, generating an optree like this instead:

4     <;> nextstate(main 2 -e:1) v:{ ->5
-     <1> null vK/1 ->b
7        <|> and(other->8) vK/1 ->b
6           <1> ref_seqne sKP/SCALAR,SKIPLOGOP,NEXT_AND ->7
5              <0> padsv[$x:1,2] s ->6

OP_REF_SEQNE determines the type of the operand, more efficiently compares it to the desired type (baked into the OP's flags), and directly calls the op_other or op_next of the LOGOP.

Closes #23395


  • This set of changes requires a perldelta entry, and I will add one before blead reopens.

@richardleach richardleach added the defer-next-dev This PR should not be merged yet, but await the next development cycle label Jun 7, 2026
@richardleach

Copy link
Copy Markdown
Contributor Author

Some numbers.

  • ./perl -e 'my $x = []; for (1 .. 100_000_000) { if (ref($x) eq "ARRAY") {} }'

WAS

          3,251.66 msec task-clock                       #    0.999 CPUs utilized             
                31      context-switches                 #    9.534 /sec                      
                 0      cpu-migrations                   #    0.000 /sec                      
               194      page-faults                      #   59.662 /sec                      
    14,837,068,241      cycles                           #    4.563 GHz                       
       875,383,393      stalled-cycles-frontend          #    5.90% frontend cycles idle      
    47,707,840,649      instructions                     #    3.22  insn per cycle            
    11,301,799,544      branches                         #    3.476 G/sec                     
           247,851      branch-misses                    #    0.00% of all branches

NOW

          1,281.06 msec task-clock                       #    0.999 CPUs utilized             
                 9      context-switches                 #    7.025 /sec                      
                 0      cpu-migrations                   #    0.000 /sec                      
               195      page-faults                      #  152.218 /sec                      
     5,821,338,499      cycles                           #    4.544 GHz                       
        11,401,500      stalled-cycles-frontend          #    0.20% frontend cycles idle      
    22,304,817,770      instructions                     #    3.83  insn per cycle            
     4,701,097,349      branches                         #    3.670 G/sec                     
           141,592      branch-misses                    #    0.00% of all branches
  • ./perl -e 'my $x = []; for (1 .. 100_000_000) { if (ref($x) ne "ARRAY") {} }'

WAS

          3,545.31 msec task-clock                       #    0.999 CPUs utilized             
                38      context-switches                 #   10.718 /sec                      
                 0      cpu-migrations                   #    0.000 /sec                      
               193      page-faults                      #   54.438 /sec                      
    16,268,607,156      cycles                           #    4.589 GHz                       
     3,016,573,527      stalled-cycles-frontend          #   18.54% frontend cycles idle      
    47,308,397,965      instructions                     #    2.91  insn per cycle            
    11,301,925,558      branches                         #    3.188 G/sec                     
           359,182      branch-misses                    #    0.00% of all branches

NOW

          1,353.74 msec task-clock                       #    0.999 CPUs utilized             
                11      context-switches                 #    8.126 /sec                      
                 0      cpu-migrations                   #    0.000 /sec                      
               192      page-faults                      #  141.830 /sec                      
     6,060,307,299      cycles                           #    4.477 GHz                       
        43,243,914      stalled-cycles-frontend          #    0.71% frontend cycles idle      
    22,504,867,772      instructions                     #    3.71  insn per cycle            
     4,801,108,056      branches                         #    3.547 G/sec                     
           141,410      branch-misses                    #    0.00% of all branches

In both "NOW" cases, OP_REF_SEQNE accounts for just under 30% of the run time, according to perf report.

@richardleach

Copy link
Copy Markdown
Contributor Author

Note: On the face of it, builtin::reftype seems like it could be easily incorporated, but there were some test fails when I tried to do so. I haven't dug into them yet, but it seemed like they could have been to an on-CPAN implementation of reftype rather than the builtin one.

Scalar::Util notes that "Note that for internal reasons, all precompiled regexps (qr/.../) are blessed references; thus ref() returns the package name string "Regexp" on these but reftype() will return the underlying C structure type of "REGEXP" in all capitals." It looked like builtin::reftype will behave more like ref() than Scalar::Util's reftype in this regard.

Since builtin::reftype is currently an XSUB, I'm not sure how to distinguish it's reftype OP nodes from third-party ones.

@Grinnz

Grinnz commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Scalar::Util notes that "Note that for internal reasons, all precompiled regexps (qr/.../) are blessed references; thus ref() returns the package name string "Regexp" on these but reftype() will return the underlying C structure type of "REGEXP" in all capitals." It looked like builtin::reftype will behave more like ref() than Scalar::Util's reftype in this regard.

I would consider that a serious bug, and I don't see that behavior on 5.42.2:

> perl -Mbuiltin=reftype -E'say reftype qr/foo/'
REGEXP

@richardleach

Copy link
Copy Markdown
Contributor Author

I don't see that behavior on 5.42.2

Thanks, maybe I'm misremembering. I only tried briefly to add support for builtin::reftype, saw that the changes I made had caused test failures, then backed the changes out to revisit it as a follow-up.

@jkeenan

jkeenan commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Scalar::Util notes that "Note that for internal reasons, all precompiled regexps (qr/.../) are blessed references; thus ref() returns the package name string "Regexp" on these but reftype() will return the underlying C structure type of "REGEXP" in all capitals." It looked like builtin::reftype will behave more like ref() than Scalar::Util's reftype in this regard.

I would consider that a serious bug, and I don't see that behavior on 5.42.2:

> perl -Mbuiltin=reftype -E'say reftype qr/foo/'
REGEXP

Where we're at now:

$ ./bin/perl -Ilib -v | head -n2 | tail -n1
This is perl 5, version 43, subversion 12 (v5.43.12 (v5.43.11-6-g97fa0bdf58)) built for x86_64-linux

$ ./bin/perl -Ilib -Mbuiltin=reftype -E'say reftype qr/foo/'
REGEXP

@richardleach

Copy link
Copy Markdown
Contributor Author
$ perl -Mbuiltin=reftype -E'say ref qr/foo/'
Regexp

I will double check the behaviour of this PR here!

@Grinnz

Grinnz commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Might be a little confusion; ref, as the subject of this PR, returns the class name (like 'Regexp') for blessed refs and internal reftype otherwise. builtin::reftype should have the same exact behavior as Scalar::Util::reftype, which is that it should only return uppercase internal "types" and not class names regardless of blessing status.

@richardleach

Copy link
Copy Markdown
Contributor Author

Since builtin::reftype is currently an XSUB

That's also not true. (I just misremembered the contents of builtin.c all being XSUBs.)

I'll take a look at:

  • Adding Regexp to the bitmask, since it's very common on CPAN.
  • Adding reftype support.

On a related point, when should ref and reftype start returning something for a class object?

@hvds

hvds commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

OP_REF_SEQNE determines the type of the operand, more efficiently compares it to the desired type (baked into the OP's flags), [...]

I don't see an explicit mention, can you confirm that this preserves the existing behaviour of punning classnames?

% perl -wle '$x = bless {}, "ARRAY";
  print "claims to be an array" if ref($x) eq "ARRAY"'
claims to be an array
% 

@tonycoz

tonycoz commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

On a related point, when should ref and reftype start returning something for a class object?

$ ./perl -Ilib -Mfeature=class -Mbuiltin=reftype -E 'class F {} say reftype(F->new); say ref(F->new)'
class is experimental at -e line 1.
OBJECT
F

They already do, or did I miss a point?

@tonycoz

tonycoz commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

OP_REF_SEQNE determines the type of the operand, more efficiently compares it to the desired type (baked into the OP's flags), [...]

I don't see an explicit mention, can you confirm that this preserves the existing behaviour of punning classnames?

% perl -wle '$x = bless {}, "ARRAY";
  print "claims to be an array" if ref($x) eq "ARRAY"'
claims to be an array
% 

Yes, it works:

tony@venus:.../git/perl6$ ./perl -Ilib -wle '$x = bless {}, "ARRAY"; print "claims to be an array" if ref($x) eq "ARRAY"'
claims to be an array
tony@venus:.../git/perl6$ ./perl -Ilib -MO=Concise -wle '$x = bless {}, "ARRAY"; print "claims to be an array" if ref($x) eq "ARRAY"'
f  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter v ->2
2     <;> nextstate(main 1 -e:1) v:{ ->3
7     <2> sassign vKS/2 ->8
5        <@> bless sK/2 ->6
-           <0> ex-pushmark s ->3
3           <0> emptyavhv[t2] s/ANONHASH ->4
4           <$> const[PV "ARRAY"] s ->5
-        <1> ex-rv2sv sKRM*/1 ->7
6           <#> gvsv[*x] s ->7
8     <;> nextstate(main 1 -e:1) v:{ ->9
-     <1> null vK/1 ->f
b        <|> and(other->c) vK/STMT,1 ->f
a           <1> ref_seqne sK/ARRAY,SKIPLOGOP,NEXT_AND ->b
-              <1> ex-rv2sv sK/1 ->a
9                 <#> gvsv[*x] s ->a
e           <@> print vK ->f
c              <0> pushmark s ->d
d              <$> const[PV "claims to be an array"] s ->e
-e syntax OK

Comment thread pp.c Outdated
@tonycoz

tonycoz commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Some numbers.

* `./perl -e 'my $x = []; for (1 .. 100_000_000) { if (ref($x) eq "ARRAY") {} }'`

WAS

       875,383,393      stalled-cycles-frontend          #    5.90% frontend cycles idle      

NOW

        11,401,500      stalled-cycles-frontend          #    0.20% frontend cycles idle      
* `./perl -e 'my $x = []; for (1 .. 100_000_000) { if (ref($x) ne "ARRAY") {} }'`

WAS

     3,016,573,527      stalled-cycles-frontend          #   18.54% frontend cycles idle      

NOW

        43,243,914      stalled-cycles-frontend          #    0.71% frontend cycles idle      

That's an amazing* improvement in stalled cycles.

The rest of the stats scale at least very roughly with the overall improvement but this comes close to zeroing it out.

* I'd say "ridiculous" but that might imply I don't believe the results.

@iabyn

iabyn commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

A bit of pickiness: I first read the name of the op, OP_REF_SEQNE, as "an op concerned with reference sequences not equal to". Perhaps shorten it to OP_REF_EQ or OP_REF_CMP say, to indicate that a ref is being compared to something?
Also, perhaps the OPpREF_SEQNE define would be better named something like OPpREF_SEQNE_MASK?

@bbrtj

bbrtj commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Awesome improvement. Question: will this affect ref $val eq '' or just ref $val to check whether something is a reference at all? (the second variant probably not, because of that pesky package named 0 edge case)

@leonerd

leonerd commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Yes I'm with @iabyn on this. The name doesn't quite feel right - something like OP_REF_EQ or OP_REF_CMP would be better. It doesn't have the "S" to stand for stringy, like OP_SEQ does but I've always felt that op was badly named. No reason to repeat that mistake here. And it's quite common for ops to have a private flag to negate their output, so no need to mention in its name that it might be negatable; that's a small internal detail.

Comment thread t/op/ref.t Outdated
@richardleach

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback, folks.

In no particular order:

  • Renaming it to OP_REF_CMP sounds good, with OPpREF_CMP_MASK to match.
  • If ref $val eq '' is a common thing, I'll look into including it.
  • This won't optimise (ref $val) at all - but isn't that already efficiently handled by the TRUEBOOL case in pp_ref?

@richardleach

Copy link
Copy Markdown
Contributor Author

That's an amazing* improvement in stalled cycles.

Stalled frontend cycles seems to be the noisiest metric, greatly influenced by what else the machine is doing at the time. I have an incling it's mostly noise here.

@richardleach
richardleach force-pushed the pp_ref_seq_coretype branch from 923aedd to 75c2d75 Compare June 8, 2026 22:33
@richardleach

Copy link
Copy Markdown
Contributor Author

OP now renamed, copy-pasta error in t/op/ref.t fixed, redundant code in Perl_ck_scmp also removed.

Supporting reftype and then refactoring the "dodgy type check" code next.

@Grinnz

Grinnz commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
  • If ref $val eq '' is a common thing, I'll look into including it.

I see a non-trivial amount of this on CPAN in a cursory search.

@leonerd

leonerd commented Jun 9, 2026

Copy link
Copy Markdown
Contributor
  • If ref $val eq '' is a common thing, I'll look into including it.

I see a non-trivial amount of this on CPAN in a cursory search.

It can be seen as an interesting defence against bless $obj, "0"

@richardleach

Copy link
Copy Markdown
Contributor Author

@leonerd - ref isn't eligible for constant folding (according to embed.fnc) but reftype was added with the folding flag. Was there a specifc reason for the difference?

Asking 'cos it's causing problems with adding support for reftype here, because the op_next chain gets partially established by the folding attempt prior to arriving at the ref_cmp examination, consequently the next LINKLIST run fails to descend down the optree fragment as far as it needs to.

Just checking if I need to figure out how to work with that, or if we can just knock off the folding flag for reftype. :)

@richardleach
richardleach force-pushed the pp_ref_seq_coretype branch from 75c2d75 to 4317189 Compare June 9, 2026 22:22
@richardleach
richardleach marked this pull request as draft June 9, 2026 22:25
@richardleach
richardleach force-pushed the pp_ref_seq_coretype branch 3 times, most recently from 01f2e78 to ff6d4af Compare June 15, 2026 21:41
Comment thread sv.h
@richardleach
richardleach force-pushed the pp_ref_seq_coretype branch from ff6d4af to e919ee3 Compare June 17, 2026 08:28
@richardleach
richardleach marked this pull request as ready for review June 17, 2026 08:41
Comment thread regen/op_private Outdated
@richardleach richardleach changed the title Add OP_REF_SEQNE to optimize (ref $x eq/ne 'SCALAR') patterns Add OP_REF_CMP to optimize (ref $x eq/ne 'SCALAR') patterns Jul 5, 2026
@richardleach
richardleach force-pushed the pp_ref_seq_coretype branch from e61f034 to e21ff04 Compare July 5, 2026 15:04
@leonerd leonerd removed the defer-next-dev This PR should not be merged yet, but await the next development cycle label Jul 16, 2026
@richardleach

Copy link
Copy Markdown
Contributor Author

Added a final commit for the perldelta entry.

Finding value of `ref`, comparing it to a specific one of Perl's
builtin types, and doing something with that boolean result is
currently a fair bit of work.

    if (ref $x eq 'SCALAR) { # do something }

or

    if (ref $x ne 'SCALAR) { # do something }

The type of `$x` has to be determined and the string value for it
pushed onto the stack, the value to compare it to is also pushed,
a string comparison is then performed, with `&PL_sv_yes` or
`&PL_sv_no` put on the stack to be consumed by a LOGOP in order to
determine the control flow.

The optree looks something like this:

    4     <;> nextstate(main 2 -e:1) v:{ ->5
    -     <1> null vK/1 ->d
    9        <|> and(other->a) vK/1 ->d
    8           <2> seq sKP/2 ->9
    6              <1> ref[t2] sK/1 ->7
    5                 <0> padsv[$x:1,2] s ->6
    7              <$> const[PV "SCALAR"] s/BARE ->8

This commit instead adds an OP_REF_CMP optimization, generating
an optree like this instead:

    4     <;> nextstate(main 2 -e:1) v:{ ->5
    -     <1> null vK/1 ->b
    7        <|> and(other->8) vK/1 ->b
    6           <1> ref_cmp sKP/SCALAR,SKIPLOGOP,AND ->7
    5              <0> padsv[$x:1,2] s ->6

OP_REF_CMP determines the type of the operand, more efficiently
compares it to the desired type (baked into the OP's flags),
and directly calls the `op_other` or `op_next` of the LOGOP.

The existing classname handling behaviour is preserved.

    $x = bless {}, "ARRAY";
    if (ref $x eq "ARRAY") {} # This *is* still true.

This commit also optimizes the same sort of patterns that use `reftype`
instead of `ref`. The behaviour differences between the two are
preserved.

As there were spare bits available, some additional common comparators
have also been baked in:
* `Regexp` - for the `qr//` case with `ref`
* `''` - the empty string
To avoid duplication of "what reference type is this?" logic across
`Perl_sv_reftype` and `pp_ref_cmp`, this commit:

* Renames `OPpREF_CMP_*` constants to `SVrt_*` equivalents
* Adds a `PL_sv_reftype_lookup` table ordered to match those constants
* Adds a `Perl_sv_reftype_id` function that takes the logic from
  `Perl_sv_reftype` but returns only an index ID value
* `Perl_sv_reftype` uses that new function and table to return strings
* `pp_ref_cmp` uses the function and compares to the expected ID value
@richardleach
richardleach force-pushed the pp_ref_seq_coretype branch from 2b67e1e to 3ba1f94 Compare July 17, 2026 14:30
@richardleach

Copy link
Copy Markdown
Contributor Author

Now with a bump to lib/B/Op_private.pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Single pp OP for "if(ref($self) eq 'REFTYPE')" ?

9 participants