מדידה#

אי אפשר לכוונן את מה שלא מדדתם, ועל 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 והזמן המושקע בכל אחד מהם. השתמשו בו.

ה־profiler של pperl#

ה־profiler של pperl מהודר לתוך בנייה ייעודית ומופעל בזמן ריצה על ידי משתנה סביבה. הוא כבוי כברירת מחדל ואינו עולה דבר בבנייה רגילה.

בנו פעם אחת עם התכונה profile, ולאחר מכן הריצו כאשר PPERL_PROFILE מוגדר:

cargo build --release --features=profile
PPERL_PROFILE=1 ./target/release/pperl script.pl

כאשר התוכנית מסתיימת - או על SIGINT / SIGTERM, כך שניתן עדיין לבצע profiling לתוכנית ארוכת־ריצה או תקועה על ידי קטיעתה

  • הדוח מודפס אל STDERR. יש בו שני חלקים.

תזמוני תת־שגרה, הרשומות המובילות לפי הזמן הכולל:

-- Subroutine Timings (top 20 by total time) --
Sub                                           Calls     Total ms     Avg µs
----------------------------------------------------------------------------
main::parse_record                            10000       412.318      41.23
main::normalise                              120000        88.004       0.73

תזמוני op, הרשומות המובילות לפי הזמן הכולל, המציינות את פעולת המפרש ולא שורה במקור שלכם:

-- Op Timings (top 30 by total time) --
Op                                Count     Total ms     Avg ns
----------------------------------------------------------------
helem                          12400000      210.114         16
padsv                          31000000      180.700          5

קראו תחילה את חלק תת־השגרה: הוא אומר לכם היכן בקוד שלכם הולך הזמן. קראו את חלק ה־op כאשר חלק תת־השגרה כבר הצביע לכם על שגרה ואתם רוצים לדעת איזה סוג של עבודה שולט בה - שגרה עתירת helem מבצעת חיפושים בהאש; אחת עתירת padsv נוגעת במשתנים לקסיקליים; אחת שזמנה ב־subcall משלמת תקורת קריאה ששווה ל־inline.

ה־profiler סופר את מה שהמפרש מבצע. קוד שה־JIT הידר לקוד מכונה נייטיבי אינו רץ דרך דיספאץ« ה־op של המפרש, ולכן אינו מופיע op אחר op. לולאה שנעלמה מתזמוני ה־op היא לולאה שה־JIT השתלט עליה - וזו התשובה שרציתם. כדי לבצע profiling להתנהגות המפרש בבידוד, השביתו את ה־JIT באמצעות --no-jit (ראו להלן).

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

כאשר השאלה היא ”כמה מהיר 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 העוטפת את הקוד ב־sub { ... } מודדת את הקוד כגוף תת־שגרה. גופי תת־שגרה אינם מהודרים ב־JIT - ה־JIT מכוון ללולאות, לא למסגרות קריאה (ראו הידור JIT). כך לולאה מספרית הדוקה שהייתה מהודרת בהיקף הקובץ עשויה לרוץ מפורשת בתוך callback של Benchmark, וההשוואה ממעיטה במהירות האמיתית של הלולאה. כאשר הדבר שאתם מתזמנים הוא לולאה שאמורה לעבור JIT, העדיפו את bench/run-perlbench או טווח Time::HiRes סביב הלולאה במקומה.

כיבוי ה־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. הריצו את התוכנית פעם אחת תחת בניית ה־profiler. קראו את תזמוני תת־השגרה. שתיים או שלוש השורות העליונות הן התקציב שלכם.

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

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

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

ראו גם#

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

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

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

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

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