Perl 7 Feature Request: versiegelte Unterteile für typisierte Lexika

[ARCHIVIERT] Zuletzt aktualisiert von Joe Schaefer auf Mo., 16 Feb. 2026    Quelle

 

Sonnenstern.

Das Problem

Der OO-Laufzeitmethoden-Lookup von Perl 5 hat 50% mehr Performance-Overhead als ein direkter, benannter Subroutinen-Aufruf.

Die erste Lösung: Methoden-Lookup-Optimierungen von Doug MacEachern

Doug war der Schöpfer des mod_perl-Projekts zurück in der Mitte von 90s, so offensichtlich schreiben hohe Leistung Perl war sein forté. Einer seiner vielen Beiträge zu p5p die Performanceeinbußen beim Lookup-Overhead der OO-Methode mit einer Methode + um die Hälfte reduzieren @ISA-Hierarchiecache, um die Laufzeitobjektmethode für mod_perl-Objekte wie Apache2::RequestRec möglichst rationalisiert. Aber es bringt uns nur auf halbem Weg dorthin.

Dies ist kein trifling Problem mit Anrufen an C-Struktur get-set-Accessor-Methoden — die allgemeine Situation mit vielen mod_perl-APIs. Perls Runtime-Methode-Aufruf-Lookup-Strafe für HTTPDs Struktur request_rec *, die mod_perl über die Apache2::RequestRec Das Modul befindet sich in der gleichen Größenordnung wie die vollständige Ausführung des Aufrufs. Für von mod_perl unterstützte Sites, die Millionen von XS-Methodenaufrufen pro Sekunde ausführen, ist dies eine schreckliche Verschwendung wertvoller CPU-Zyklen.

Was Doug sucht nach war eine Möglichkeit, Perl 5 anzuweisen, die Methodensuche zur Kompilierungszeit auszuführen, wie dies bei benannten Subroutinenaufrufen der Fall ist. Jedes Mal, wenn Doug es versuchte, traf er Straßensperren entweder sozialer oder technischer Natur. Vielleicht ist es an der Zeit, mit dem Aufkommen von Perl 7 einen weiteren Pass auf diese Idee zu machen.

Benchmark-Skript.

#!/usr/bin/env -S perl -Ilib -Iblib/arch
use Test::More tests => 3;
use POSIX 'dup2';
dup2 fileno(STDERR), fileno(STDOUT);
use strict;
use warnings;
use Benchmark ':all';
our ($x, $z);
$x = bless {}, "Foo";
$z = Foo->can("foo");
sub method {$x->foo}
sub class  {Foo->foo}
sub anon   {$z->($x)}
sub bar    { 1 }
sub reentrant;
BEGIN {
  package Foo;
  use sealed 'all';
  sub foo { shift }
  my $n;
  sub _foo { my main $x = shift; $n++ ? $x->bar : $x->reentrant }
}
sub func   {Foo::foo($x)}

BEGIN {our @ISA=qw/Foo/}
use base 'sealed';
use sealed 'deparse';

my main $y; #sealed src filter transforms this into: my main $y = 'main';

sub sealed :Sealed {
    $y->foo();
}

sub also_sealed :Sealed {
    my main $a = shift;
    if ($a) {
        my Benchmark $bench;
        my $inner = $a;
        return sub :Sealed {
            my Foo $b = $a;
            $inner->foo($b->foo($inner->bar, $inner, $bench->cmpthese));
            $a = $inner;
            $a->foo;
            $b->bar; # error!
          };
    }
    $a->bar();
}

sub reentrant :Sealed { my main $b = shift; local our @Q=1; my $c = $b->_foo }

ok($y->reentrant()==1);

my %tests = (
    func => \&func,
    method => \&method,
    sealed => \&sealed,
    class => \&class,
    anon => \&anon,
);

cmpthese 20_000_000, \%tests;

ok(1);

use constant LOOPS => 3;

sub method2 {
  my $obj = "main";
  for (1..LOOPS) {
    $obj->foo;
    $obj->bar;
    $obj->reentrant;
  }
}

sub sealed2 :Sealed {
  my main $obj; # sealed-src-filter
  for (1..LOOPS) {
    $obj->foo;
    $obj->bar;
    $obj->reentrant;
  }
}

cmpthese 1_000_000, {
  method => \&method2,
  sealed => \&sealed2,
};

ok(1);

Benchmark-Ergebnisse

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

Vorgeschlagene Lösung Perl 7: :versiegelt Subroutinen für typisierte Lexika

Beispielcode:

use v5.38;
use Apache2::RequestRec;

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

Produktionsqualität, Robustes Perl v5.28+ Prototyp: sealed.pm v8.7.0 (auf CPAN).

Kompilierungsanweisungen für perl 5.30+ sind in der sealed.pm pod, wenn Sie mod_perl2 mit ithreads und httpd-2.4 mit Ereignis-pm ausführen möchten, und nicht segfault auf any-Skala. Getestet am Solaris 11.4 und Ubuntu 22.04 auf amd64.

Zum Spaß, probieren Sie das Affe Patch bis ModPerl::RegistryCooker:


<VirtualHost *:443>
    PerlModule ModPerl::RegistryCookerSealed
    PerlResponseHandler ModPerl::Registry
    AddHandler perl-script .pl
    Options +ExecCGI
</VirtualHost>

Es ermöglicht die Wirkung von Unter-Handler: Verschlüsselte {script goes here} auf alle Ihre ModPerl::Registry Skripte, etwas wie diese.

~/src/cms% h2load -n 100000 -c 1000 -m 100 -t 10 http://localhost/perl-script/enquiry.pl\?lang=.es
starting benchmark...
spawning thread #0: 100 total client(s). 10000 total requests
spawning thread #1: 100 total client(s). 10000 total requests
spawning thread #2: 100 total client(s). 10000 total requests
spawning thread #3: 100 total client(s). 10000 total requests
spawning thread #4: 100 total client(s). 10000 total requests
spawning thread #5: 100 total client(s). 10000 total requests
spawning thread #6: 100 total client(s). 10000 total requests
spawning thread #7: 100 total client(s). 10000 total requests
spawning thread #8: 100 total client(s). 10000 total requests
spawning thread #9: 100 total client(s). 10000 total requests
Application protocol: h2c
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 13.07s, 7652.14 req/s, 11.83MB/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: 154.61MB (162119955) total, 566.39KB (579980) headers (space savings 95.47%), 152.30MB (159700000) data
                       min         max         mean         sd        +/- sd
time for request:     5.74ms      12.77s       6.39s       3.61s    58.14%
time for connect:      304us    293.01ms     70.17ms     76.83ms    74.80%
time to 1st byte:     7.86ms       7.87s       3.33s       1.82s    50.40%
req/s           :       7.71      248.17       19.60       28.07    92.70%

Siehe https://github.com/SunStarSys/sealed/blob/master/lib/sealed.pm. Suchen nach t/bench.pl im übergeordneten Verzeichnis.

Dadurch kann Perl 5 den Beispielcode ausführen content_type Methoden-Lookup zur Kompilierungszeit, ohne Back-Compat-Probleme oder verschlimmerte CPAN-Codierer zu verursachen, da diese Funktion auf Anwendungsentwickler ausgerichtet wäre. Keine vererbbaren OO-Modulautoren.

Diese perlish Idee ist unentgeltlich gestohlen von Dylan. Lesen Sie dies für die CPython Anstrengung von vor mehr als einem Jahrzehnt.