Perl 7: בקשת תכונה: תת חתום לקסיקלים מוקלדים
.
הבעיה
ל-lookup של שיטת זמן הריצה OO של Perl 5 יש תקורת ביצועים גבוהה ב-50% בהשוואה להפעלה ישירה בשם שגרת משנה.
הפתרון הראשוני: אופטימיזציות קוד השיטה של דאג מקאשרן
דאג היה היוצר של הפרויקט mod_perl בחזרה באמצע 90s, אז ברור שכתיבת ביצועים גבוהים פרל היה המבצר שלו. אחת התרומות הרבות שלו ל p5p היה לחתוך את קנס הביצועים של תקורת lookup של שיטת OO לחצי, באמצעות השיטה + @מטמון היררכיה של ISA כדי להפוך את קוד שיטת אובייקט זמן הריצה לאובייקטים של mod_perl כמו Apache2::RequestRec יעיל ככל האפשר. זה רק מחצית הדרך לשם.
זו אינה בעיה עם קריאות ל- מבנה C שיטות get-set accessor — המצב הנפוץ עם ממשקי API רבים של mod_perl. קנס lookup לשיטת זמן ריצה של פרל על httpd’s מבנה request_rec *, ש-mod_perl חושף דרך Apache2::RequestRec מודול, נמצא באותו סדר גודל של ביצוע מלא של השיחה. עבור mod_perl אתרים מגובים ביצוע מיליוני שיטת XS קורא שנייה, זה בזבוז נורא של מחזורי CPU יקרים.
מה דאג חיפש הייתה דרך לומר לפרל 5 לבצע את חיפוש השיטה בזמן ההידור, כפי שהוא עושה עם קריאות שגרתיות בשם. בכל פעם שדאג ניסה, הוא נתקל במחסומים בעלי אופי חברתי או טכני. אולי הגיע הזמן לעבור על הרעיון הזה עם כניסתו של פרל 7.
תסריט בוחן ביצועים.
#!/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);
מס’ תוצאות בוחן ביצועים
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
פתרון פרל 7 מוצע: :חסום שגרות משנה לקסיקלים מוקלדים
קוד לדוגמה:
use v5.38;
use Apache2::RequestRec;
sub handler :Sealed (Apache2::RequestRec $r) {
$r->content_type("text/html"); #compile time method lookup
}
ייצור-איכות, פרל חזק v5.28+ אב טיפוס: sealed.pm v8.7.7 (על CPAN).
הוראות הידור עבור פרל 5.30+ זמינות ב sealed.pm אם תרצה להריץ את mod_perl2 w/ ithreads ו-httpd-2.4 w/ event mpm, ולא segfault בקנה מידה any. נבדק ב- סולאריס 11.4 וגם אובונטו 22.04 ב-amd64.
בשביל הכיף, נסו את זה טלאי קוף אל ModPerl::RegistryCooker:
<VirtualHost *:443>
PerlModule ModPerl::RegistryCookerSealed
PerlResponseHandler ModPerl::Registry
AddHandler perl-script .pl
Options +ExecCGI
</VirtualHost>
זה מאפשר את ההשפעות של sub handler: {script חתום כאן} על כל שלך ModPerl::רישום תסריט, משהו כמו זה אחד.
~/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%
ראה https://github.com/SunStarSys/sealed/blob/master/lib/sealed.pm. חפש t/bench.pl בספריית האב.
זה יאפשר פרל 5 לעשות את הקוד לדוגמה content_type שיטה-lookup בזמן ההידור, מבלי לגרום לבעיות back-compat או מקודדים CPAN אגורה, שכן תכונה זו הייתה מכוונת למפתחי יישומים. לא מחברים מודול OO הניתנים לירושה.
הרעיון הזה נגנב בחינם. דילן. קרא את זה למאמץ CPython מלפני יותר מעשור.
