Perl 7 Feature Request: sealed subs for typed lexicals

[ARCHIVED] Last updated by Joe Schaefer on Fri, 01 May 2026    source
 

sunstarstaronly

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


Benchmark results

1..3
sealed: compiling main->foo lookup.
sub sealed :sealed {
    use warnings;
    use strict;
    $y->foo:compiled;
}
sealed: compiling Benchmark->cmpthese lookup.
sealed: compiling Foo->foo lookup.
sealed: compiling main->foo lookup.
sealed: compiling Foo->bar lookup.
sealed: tweak() aborted: sealed: invalid lookup: Foo->bar - did you forget to 'use Foo' first?
sub __ANON__ :sealed {
    use warnings;
    use strict;
    my Foo $b = $a;
    $inner->foo($b->foo:compiled($inner->bar, $inner, $bench->cmpthese:compiled));
    $a = $inner;
    $a->foo:compiled;
    $b->bar;
}
sealed: compiling main->bar lookup.
sub also_sealed :sealed {
    use warnings;
    use strict;
    my main $a = shift();
    if ($a) {
        my Benchmark $bench = 'Benchmark';
        my $inner = $a;
        return sub {
            my Foo $b = $a;
            $inner->foo($b->foo:compiled($inner->bar, $inner, $bench->cmpthese:compiled));
            $a = $inner;
            $a->foo:compiled;
            $b->bar;
        }
        ;
    }
    $a->bar:compiled;
}
sealed: compiling main->_foo lookup.
sub reentrant :sealed {
    use warnings;
    use strict;
    my main $b = shift();
    (local our(@Q)) = 1;
    my $c = $b->_foo:compiled;
}
sealed: compiling main->foo lookup.
sealed: compiling main->bar lookup.
sealed: compiling main->reentrant lookup.
sub sealed2 :sealed {
    use warnings;
    use strict;
    my main $obj = 'main';
    foreach $_ (1 .. 3) {
        $obj->foo:compiled;
        $obj->bar:compiled;
        $obj->reentrant:compiled;
    }
}
sealed: compiling main->reentrant lookup.
sealed: compiling main->bar lookup.
sub _foo :sealed {
    package Foo;
    use warnings;
    use strict;
    my main $x = shift();
    $n++ ? $x->bar:compiled : $x->reentrant:compiled;
}
ok 1
             Rate  class method   anon   func sealed
class  16129032/s     --    -4%   -26%   -33%   -36%
method 16806723/s     4%     --   -23%   -30%   -34%
anon   21739130/s    35%    29%     --   -10%   -14%
func   24096386/s    49%    43%    11%     --    -5%
sealed 25316456/s    57%    51%    16%     5%     --
ok 2
           Rate method sealed
method 546448/s     --   -17%
sealed 662252/s    21%     --
ok 3

Proposed Perl 7 solution: :sealed subroutines for typed lexicals

Sample code:

use v5.38;
use Apache2::RequestRec;

sub handler :Sealed (Apache2::RequestRec $r) {
  $r->content_type("text/html"); #compile time method lookup
}

Production-Quality, Robust Perl v5.28+ Prototype: sealed.pm v8.7.7 (on CPAN)

Compile instructions for perl 5.30+ are available in the sealed.pm pod should you want to run mod_perl2 w/ ithreads and httpd-2.4 w/ event mpm, and not segfault at any scale. Tested on Solaris 11.4 and Ubuntu 22.04 on amd64.

For fun, try this monkey patch to ModPerl::RegistryCooker:


It enables the effects of sub handler :Sealed {script goes here} on all of your ModPerl::Registry scripts, something like this one.

Here is a live benchmark against a production server with a normal daily traffic tune. Unbeatable HTTP/2 performance with mod_perl, libapreq2 and sealed.pm:

% h2load -n 100000 -m 100 -c 100 -H 'Accept-Encoding: br' 'https://www.sunstarsys.com/dynamic/enquiry?lang=.en'
starting benchmark...
spawning thread #0: 100 total client(s). 100000 total requests
TLS Protocol: TLSv1.3
Cipher: TLS_AES_256_GCM_SHA384
Server Temp Key: X25519 253 bits
Application protocol: h2
progress: 10% done
progress: 20% done
progress: 30% done
progress: 40% done
progress: 50% done
progress: 60% done
progress: 70% done
progress: 80% done
progress: 90% done
progress: 100% done

finished in 114.30s, 874.88 req/s, 1.15MB/s
requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 131.03MB (137391089) total, 2.29MB (2398165) headers (space savings 97.88%), 127.02MB (133189224) data
                     min         max         mean         sd        +/- sd
time for request:    87.93ms      36.90s       9.29s       6.61s    61.44%
time for connect:   435.07ms       2.05s       1.19s    444.88ms    59.00%
time to 1st byte:      2.05s       5.28s       2.79s    627.57ms    83.00%
req/s           :       8.75       46.43       15.17       13.68    82.00%

See https://github.com/SunStarSys/sealed/blob/master/lib/sealed.pm. Look for t/bench.pl in the parent directory.

This will allow Perl 5 to do the sample code’s 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 inheritable OO-module authors.

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