Perl 7 Funktionsbegäran: förseglade subs för typangivna lexikaler
.
Problemet
Uppslagningen av OO-exekveringsmetod för Perl 5 har 50 % högre prestandakostnader än ett direkt, namngivet subrutinanrop.
Den första lösningen: Doug MacEacherns metoduppslagsoptimeringar
Doug var skaparen av mod_perl projektet tillbaka i mitten av 90s, så uppenbarligen skriva högpresterande Perl var hans forté. Ett av hans många bidrag till p5p skulle halvera prestandapåföljden för OO-metodens uppslagskostnader med en metod + @Hierarkicache för ISA för att göra uppslagning av objektmetod vid exekvering för mod_perl-objekt som Apache2::RequestRec så effektiv som möjligt. Men det tar oss bara halvvägs dit.
Detta är inte ett problem med samtal till C-struktur get-set-åtkomstmetoder — Den gemensamma situationen med många mod_perl API:er. Perls exekveringsmetod - straffavgift för anropssökning på httpd struktur request_rec *, som mod_perl exponerar via Apache2::RequestRec modulen, är i samma storleksordning som det fullständiga utförandet av samtalet. För mod_perl backade webbplatser som gör miljontals XS-metodanrop en sekund, är detta ett fruktansvärt slöseri med värdefulla CPU-cykler.
Vad Doug söker var ett sätt att tala om för Perl 5 att utföra metodsökning vid kompilering, hur det gör med namngivna subrutinanrop. Varje gång Doug försökte, slog han vägspärrar av antingen social eller teknisk natur. Kanske är det dags att göra ett nytt pass på denna idé med tillkomsten av Perl 7.
Referensskript.
#!/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);
Riktmärkesresultat
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
Föreslagen lösning perl 7: :förseglad underrutiner för typangivna lexikaliska
Exempelkod:
use v5.38;
use Apache2::RequestRec;
sub handler :Sealed (Apache2::RequestRec $r) {
$r->content_type("text/html"); #compile time method lookup
}
Produktionskvalitet, Robust Perl v5.28+ Prototyp: sealed.pm v8.7.0 (på CPAN).
Kompileringsinstruktioner för perl 5.30+ finns i sealed.pm podden ska du köra mod_perl2 med ithreads och httpd-2.4 w/ event mpm, och inte segfault vid any-skalan. Testad den Solaris 11.4 och Ubuntu 22.04 på amd64.
För skojs skull, prova detta apa till ModPerl::RegistryCooker:
<VirtualHost *:443>
PerlModule ModPerl::RegistryCookerSealed
PerlResponseHandler ModPerl::Registry
AddHandler perl-script .pl
Options +ExecCGI
</VirtualHost>
Det möjliggör effekterna av underhanterare: förseglad {script go here} på alla dina ModPerl::Register Skript, ungefär som denna.
~/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%
Se https://github.com/SunStarSys/sealed/blob/master/lib/sealed.pm. Sök efter t/bench.pl i den överordnade katalogen.
Detta gör det möjligt för Perl 5 att göra provkodens content_type metod-lookup vid kompileringstid, utan att orsaka några back-compat problem eller aggrieved CPAN kodare, eftersom denna funktion skulle rikta applikationsutvecklare. Ej ärftliga OO-modulförfattare.
Denna Perlish idé är gratis stulen från Dylan. Läs detta för CPython-insatsen för över ett decennium sedan.
