מדידה#

אי אפשר לכוונן את מה שלא מדדתם, ועל pperl כלי המדידה הרגילים מתחלקים לשלוש קבוצות: אלה שעובדים ללא שינוי, אלה שעובדים משום שהם Perl רגיל הרץ על מפרש מהיר יותר, וזה שכולם נשענים עליו ואינו עובד כלל. פרק זה ממיין אותם ומראה כיצד לקרוא את מה שהעובדים מספרים לכם.

הכלי שאינו עובד: Devel::NYTProf#

Devel::NYTProf הוא ה־profiler הסטנדרטי ברמת שורה ותת־שגרה ב־Perl מהמעלה־הזרם. הוא מודול XS - הוא מתחבר אל המפרש דרך הרחבת C. pperl אינו כולל שכבת XS, ולכן Devel::NYTProf אינו נטען. כך גם כל profiler אחר מבוסס XS (Devel::DProf, מסלולי ה־XS של Devel::Profiler).

זהו אינו פער שיש לעקוף בעזרת מימוש מחדש ב־Pure Perl של פורמט הפלט של NYTProf. ל־pperl יש profiler משלו, מובנה בתוך סביבת הריצה, הרואה את מה שהמפרש מבצע בפועל - מוני op והזמן המושקע בכל אחד מהם. השתמשו בו.

Where the time goes: the JIT log#

pperl does not currently ship a line-or-sub profiler of its own (the one it once had belonged to a retired execution engine and never saw the current runtime). What it does ship is precise visibility into the optimization layer, which for pperl-specific tuning is usually the question you are actually asking: is my hot loop compiled, and if not, why not?

  • --jit-stats prints counters at exit: candidates found, loops compiled, entries run compiled, guard failures, deopts.

  • PPERL_JIT_LOG=/path/file appends one line per compiler event. The compile skip reason=... lines name the exact operation that kept a loop interpreted.

For symbol-level CPU attribution, use the platform profiler on a release build - perf record ./target/release/pperl script.pl then perf report. pperl is a native binary; perf sees straight through it, including JIT-compiled frames.

תזמון התוכנית כולה: מריץ מדדי הביצועים#

כאשר השאלה היא ”כמה מהיר pperl על זה, לעומת ה־perl של המערכת, עם ובלי JIT ומקביליות“, מריץ מדדי הביצועים של הפרויקט עונה עליה ישירות. bench/run-perlbench תמיד מריץ את /usr/bin/perl כבסיס (מנורמל ל־100) ומשווה את pperl בעד ארבע תצורות.

./bench/run-perlbench                # no JIT, no parallel
./bench/run-perlbench +J             # add the JIT-enabled run
./bench/run-perlbench +P             # add the parallel run
./bench/run-perlbench +J+P           # all four combinations
./bench/run-perlbench -t arith       # only benchmarks matching "arith"

כל מריץ חולק מספר איטרציות אחד, הנגזר מקצב הלולאה־הריקה של המריץ האיטי ביותר, כך שהיחסים המדווחים משווים דומה עם דומה. העמודות +J / +P הן בדיוק ההשוואה שאתם רוצים בעת ההחלטה אם קטע קוד נהנה מה־JIT או מהמקבילן: אם +J אינו מהיר יותר מהריצה הרגילה, הלולאה אינה מהודרת, וכתיבת pperl מהיר מסביר מדוע ומה לשנות.

כלי Perl רגיל שעובדים: Benchmark ו־Time::HiRes#

כלי המדידה הקלאסיים שבתוך התוכנית הם Perl רגיל. הם רצים על pperl דרך נתיב המודולים הרגיל - Benchmark נגיש בנתיב ההכללה הסטנדרטי ומתבצע כ־Perl על מפרש pperl; Time::HiRes מסופק נייטיבית. שניהם מתנהגים בדיוק כפי שמתכנת Perl מצפה.

Benchmark להשוואת A/B בין שתי דרכים לכתוב את אותה שגרה:

use Benchmark qw(cmpthese);

my @data = (1 .. 100_000);

cmpthese(-3, {            # run each for ~3 CPU-seconds
    grep_count => sub { my $n = grep { $_ % 2 == 0 } @data },
    loop_count => sub { my $n = 0; $_ % 2 or $n++ for @data },
});

Time::HiRes לתזמון שעון־קיר אד־הוק של בלוק יחיד:

use Time::HiRes qw(time);

my $t0 = time;
do_the_work();
printf "took %.3f s\n", time - $t0;

אזהרה החשובה על pperl יותר מאשר במעלה־הזרם: Benchmark מפעיל את הקוד שלכם על ידי קריאה להפניית קוד שוב ושוב, והשיגור לכל־קריאה הזה הוא התקורה של Benchmark עצמו, לא של הקוד שלכם. לולאה בתוך ה־callback עדיין מהודרת ב־JIT (לולאות מהדרות היכן שהן חיות, בהיקף הקובץ או בגוף תת־שגרה), אך עבור גופים קטנים עלות קריאת הרתמה שולטת וההשוואה ממעיטה במהירות האמיתית. כאשר הדבר שאתם מתזמנים הוא לולאה שאמורה לעבור JIT, העדיפו את bench/run-perlbench או טווח Time::HiRes סביב הלולאה במקומה (ראו הידור JIT).

כיבוי ה־optimisers לבידוד סיבה#

שני דגלים מאפשרים לכם לייחס שיפור מהירות - או האטה - לשכבה הנכונה:

pperl --no-jit script.pl          # interpreter only, no native compilation
pperl --no-parallel script.pl     # single-threaded, no Rayon dispatch

הריצו תוכנית בשלוש דרכים - ברירת מחדל, --no-jit, --no-parallel - וההבדלים אומרים לכם איזה optimiser נושא את העבודה. אם --no-jit בקושי משנה את הזמן, ה־JIT לא נכנס לפעולה על קוד זה, וצורת הלולאה היא הדבר שיש לבחון. אם --no-parallel בקושי משנה אותו, המקבילן דחה את הלולאה - בדרך כלל בשל תופעת לוואי שלא הצליח לשלול (ראו ביצוע מקבילי).

משמעת מדידה#

  1. מדדו את זמן ברירת המחדל של התוכנית מול --no-jit מול --no-parallel. ההבדלים אומרים לכם איזו שכבה נושאת את העבודה.

  2. אם שגרה חמה היא לולאה שציפיתם שתהודר, אשרו זאת באמצעות run-perlbench +J או על ידי תזמונה --no-jit לעומת ברירת המחדל. לולאה שאינה מואצת תחת ה־JIT מעוצבת לא נכון, לא איטית.

  3. רק כעת שנו קוד. מדדו מחדש באותה דרך. שמרו את השינוי אם המספר זז והתוכנית עדיין מפיקה את אותו פלט.

טענות ביצועים זולות; דוח ה־profiler ויחס מדד הביצועים אינם. בטחו במדידה.

ראו גם#

  • כתיבת pperl מהיר - לאחר שמצאתם את הלולאה החמה, כיצד לעצב אותה כך שה־JIT והמקבילן ייקחו אותה.

  • ניבים - אילו אופטימיזציות קלאסיות עדיין שווה ליישם לפני שאתם נשענים על ה־profiler.

  • הידור JIT - מה ה־JIT מהדר ומדוע תזמוני ה־op המפורשים נעלמים עבור לולאות מהודרות.

  • ביצוע מקבילי - מה העמודה +P והדגל --no-parallel מפעילים.

  • ביצועי ביטויים רגולריים - אם ה־profiler מצביע על ביטוי רגולרי, הסיבה והתרופה שוכנות שם, לא כאן.