Perl 7 Feature Request: sealed subs for typed lexicals

The Problem

Perl 5’s OO runtime method lookup has 50% more performance overhead than a direct, named subroutine invocation.

The initial solution: Doug MacEachern’s method lookup optimizations

Doug was the creator of the mod_perl project back in the mid-90s, so obviously writing high performance Perl was his forté. One of his many contributions to p5p was to cut the performance penalty of OO method lookup overhead in half, by using a method + @::ISA heirarchy cache to make the runtime object method lookup for mod_perl objects like Apache2::RequestRec as streamlined as possible. But it only gets us half-way there.

This isn’t a trifling issue with calls to C struct get-set accessor methods — the common situation with many mod_perl APIs. Perl’s runtime method-call lookup penalty on httpd’s struct request_rec *, that mod_perl exposes via the Apache2::RequestRec module, is on the same order of magnitude of the full execution of the call. For mod_perl backed sites making millions of XS method calls a second, this is an awful waste of precious CPU cycles.

What Doug was looking for was a way to tell Perl 5 to perform the method lookup at compile time, the way it does with named subroutine calls. Every time Doug tried, he hit roadblocks of either a social or technical nature. Perhaps it’s time to make another pass at this idea with the advent of Perl 7.

Benchmark script

  1. use strict;
  2. use Benchmark ':all';
  3. our ($x, $z);
  4. $x = bless {}, "Foo";
  5. $z = Foo->can("foo");
  6. sub method {$x->foo}
  7. sub class {Foo->foo}
  8. sub anon {$z->($x)}
  9. BEGIN {
  10. package Foo;
  11. use sealed 'deparse';
  12. use base 'sealed';
  13. sub foo { shift }
  14. sub bar { shift . "->::Foo::bar" }
  15. }
  16. sub func {Foo::foo($x)}
  17. BEGIN{@::ISA=('Foo')}
  18. my main $y = $x;
  19. sub sealed :Sealed {
  20. $y->foo();
  21. }
  22. sub also_sealed :Sealed {
  23. my main $a = shift;
  24. if ($a) {
  25. my Benchmark $bench;
  26. my $inner;
  27. return sub :Sealed {
  28. my Foo $b = $a;
  29. $b->foo($bench->cmpthese, $inner);
  30. $a->foo;
  31. };
  32. }
  33. $a->bar();
  34. }
  35. my %tests = (
  36. func => \&func,
  37. method => \&method,
  38. sealed => \&sealed,
  39. class => \&class,
  40. anon => \&anon,
  41. );
  42. print sealed(), "\n", also_sealed($y), "\n";
  43. cmpthese 10_000_000, \%tests;

Benchmark results

  1. sealed: compiling main->foo lookup.
  2. {
  3. use strict;
  4. $y->;
  5. }
  6. sealed: compiling Benchmark->cmpthese lookup.
  7. sealed: compiling Foo->foo lookup.
  8. sealed: compiling main->foo lookup.
  9. {
  10. use strict;
  11. my Foo $b = $a;
  12. $b->($bench->, $inner);
  13. $a->;
  14. }
  15. sealed: compiling main->bar lookup.
  16. {
  17. use strict;
  18. my main $a = shift();
  19. if ($a) {
  20. my($bench, $inner);
  21. return sub {
  22. my Foo $b = $a;
  23. $b->($bench->, $inner);
  24. $a->;
  25. }
  26. ;
  27. }
  28. $a->;
  29. }
  30. Foo=HASH(0x415fb0)
  31. CODE(0x4b73c0)
  32. Rate class method anon sealed func
  33. class 2028398/s -- -4% -30% -34% -36%
  34. method 2118644/s 4% -- -27% -31% -33%
  35. anon 2906977/s 43% 37% -- -5% -8%
  36. sealed 3058104/s 51% 44% 5% -- -3%
  37. func 3154574/s 56% 49% 9% 3% --

Proposed Perl 7 solution: :sealed subroutines for typed lexicals

Sample code:

  1. use v7.0;
  2. use Apache2::RequestRec;
  3. sub handler :sealed {
  4. my Apache2::RequestRec $r = shift;
  5. $r->content_type("text/html"); #compile time method lookup
  6. }

Production-Quality Perl 5 Prototype: sealed.pm v1.0.6

See https://github.com/SunStarSys/cms/blob/master/lib/sealed.pm.

This will allow Perl 5 to do the content_type method-lookup at compile time, without causing any back-compat issues or aggrieved CPAN coders, since this feature would target application developers, not OO-module authors.

This idea is gratuitously stolen from Dylan. Read this for the CPython effort from about a decade ago.

$Date: 2020-09-18 11:55:03 -0400 (Fri, 18 Sep 2020) $