Perl 7 Feature Request: versiegelte Unterteile für typisierte Lexika
.
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.
