Permalink
Browse files

Use a spesh plugin to optimize .?

I explored a couple of different cases. In the first, the program was
written with .?, but in its actual usage the types that showed up were
mostly monomorphic:

    class C {
    }
    class D {
        method m() { 42 }
    }
    for ^10_000_000 {
        (rand > 0.999 ?? C !! D).?m()
    }

This case ran in 10.9s before, and 4.29s after, which is a speedup of
2.5x. Note that the `rand` is a notable cost in this program, so the
speedup to the .? itself is more than that.

Under a polymorphic case:

    class C {
    }
    class D {
        method m() { 42 }
    }
    for ^10_000_000 {
        (rand > 0.5 ?? C !! D).?m()
    }

We go from 7.60s to 4.92s, a 1.5x speedup. Spesh can't just punt this
to doing a deopt for the uncommon case, because there is no uncommon
case. Still, the guard table scan comes out ahead.
  • Loading branch information...
jnthn committed Jun 8, 2018
1 parent f6f43d5 commit c6a61f563b478337a6fb6290d0d612d663d60471
Showing with 62 additions and 0 deletions.
  1. +43 −0 src/Perl6/Optimizer.nqp
  2. +19 −0 src/vm/moar/spesh-plugins.nqp
View
@@ -1397,6 +1397,9 @@ class Perl6::Optimizer {
elsif $op.name eq 'dispatch:<::>' {
return self.optimize_qual_method_call: $op;
}
elsif $op.name eq 'dispatch:<.?>' {
return self.optimize_maybe_method_call: $op;
}
}
if $op.op eq 'chain' {
@@ -2104,6 +2107,46 @@ class Perl6::Optimizer {
return $op;
}
method optimize_maybe_method_call($op) {
# Spesh plugins only available on MoarVM.
return $op unless nqp::getcomp('perl6').backend.name eq 'moar';
# We can only optimize if we have a compile-time-known name.
my $name_node := $op[1];
if nqp::istype($name_node, QAST::Want) && $name_node[1] eq 'Ss' {
$name_node := $name_node[2];
}
return $op unless nqp::istype($name_node, QAST::SVal);
# We need to evaluate the invocant only once, so will bind it into
# a temporary.
my $inv := $op.shift;
my $name := $op.shift;
my @args;
while $op.list {
nqp::push(@args, $op.shift);
}
my $temp := QAST::Node.unique('inv_once');
$op.op('stmts');
$op.push(QAST::Op.new(
:op('bind'),
QAST::Var.new( :name($temp), :scope('local'), :decl('var') ),
$inv
));
$op.push(QAST::Op.new(
:op('call'),
QAST::Op.new(
:op('speshresolve'),
QAST::SVal.new( :value('maybemeth') ),
QAST::Var.new( :name($temp), :scope('local') ),
$name,
),
QAST::Var.new( :name($temp), :scope('local') ),
|@args
));
return $op;
}
method optimize_for_range($op, $callee, $c2) {
my $code := $callee.ann('code_object');
my $count := $code.count;
@@ -1,3 +1,6 @@
## Method plugins
## Only used when the name is a constant at the use site!
# Private method resolution can be specialized based on invocant type. This is
# used for speeding up resolution of private method calls in roles; those in
# classes can be resolved by static optimization.
@@ -24,3 +27,19 @@ nqp::speshreg('perl6', 'qualmeth', -> $obj, str $name, $type {
}
}
});
# A call like `$obj.?foo` is probably worth specializing via the plugin. In
# some cases, it will be code written to be generic that only hits one type
# of invocant under a given use case, so we can handle it via deopt. Even if
# there are a few different invocant types, the table lookup from the guard
# structure is still likely faster than the type lookup. (In the future, we
# should consider an upper limit on table size for the really polymorphic
# things).
sub discard-and-nil(*@pos, *%named) { Nil }
nqp::speshreg('perl6', 'maybemeth', -> $obj, str $name {
nqp::speshguardtype($obj, $obj.WHAT);
my $meth := $obj.HOW.find_method($obj, $name);
nqp::isconcrete($meth)
?? $meth
!! &discard-and-nil
});

0 comments on commit c6a61f5

Please sign in to comment.