Μέτρηση#
Δεν μπορείτε να ρυθμίσετε ό,τι δεν έχετε μετρήσει, και στο pperl τα συνήθη εργαλεία μέτρησης χωρίζονται σε τρεις ομάδες: εκείνα που λειτουργούν αμετάβλητα, εκείνα που λειτουργούν επειδή είναι απλή Perl που τρέχει σε έναν γρηγορότερο διερμηνευτή, και εκείνο που όλοι καταφεύγουν σε αυτό και δεν λειτουργεί καθόλου. Αυτό το κεφάλαιο τα ξεδιαλύνει και δείχνει πώς να διαβάζετε όσα σας λένε εκείνα που λειτουργούν.
Το εργαλείο που δεν λειτουργεί: Devel::NYTProf#
Το Devel::NYTProf είναι ο τυπικός προφίλερ γραμμής και υπορουτίνας στην upstream Perl. Είναι άρθρωμα XS - αγκιστρώνεται στον διερμηνευτή μέσω μιας επέκτασης C. Το pperl δεν διαθέτει στρώμα XS, οπότε το Devel::NYTProf δεν φορτώνεται. Ούτε φορτώνεται οποιοσδήποτε άλλος προφίλερ βασισμένος σε XS (Devel::DProf, τα μονοπάτια XS του Devel::Profiler).
Αυτό δεν είναι ένα κενό που πρέπει να παρακαμφθεί με μια υλοποίηση του μορφότυπου εξόδου του NYTProf αμιγώς σε Perl. Το pperl έχει τον δικό του προφίλερ, ενσωματωμένο στο runtime, που βλέπει τι πραγματικά εκτελεί ο διερμηνευτής - πλήθη ops και τον χρόνο που δαπανάται στο καθένα. Χρησιμοποιήστε τον.
Ο προφίλερ του pperl#
Ο προφίλερ του pperl μεταγλωττίζεται σε μια αποκλειστική κατασκευή και ενεργοποιείται κατά τον χρόνο εκτέλεσης από μια μεταβλητή περιβάλλοντος. Είναι ανενεργός εξ ορισμού και δεν κοστίζει τίποτε σε μια κανονική κατασκευή.
Κατασκευάστε μία φορά με το χαρακτηριστικό profile, μετά τρέξτε με ορισμένη τη PPERL_PROFILE:
cargo build --release --features=profile
PPERL_PROFILE=1 ./target/release/pperl script.pl
Όταν το πρόγραμμα τερματίζει - ή με SIGINT / SIGTERM, ώστε ένα μακροχρόνιο ή κολλημένο πρόγραμμα να μπορεί ακόμη να προφιλαριστεί διακόπτοντάς το
η αναφορά τυπώνεται στο
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
Χρονομετρήσεις ops, οι κορυφαίες καταχωρίσεις ανά συνολικό χρόνο, που κατονομάζουν τη λειτουργία του διερμηνευτή αντί για μια γραμμή του πηγαίου σας κώδικα:
-- Op Timings (top 30 by total time) --
Op Count Total ms Avg ns
----------------------------------------------------------------
helem 12400000 210.114 16
padsv 31000000 180.700 5
Διαβάστε πρώτα την ενότητα υπορουτινών: σας λέει πού στον κώδικά σας πηγαίνει ο χρόνος. Διαβάστε την ενότητα ops όταν η ενότητα υπορουτινών σάς έχει στρέψει σε μια ρουτίνα και θέλετε να μάθετε τι είδους εργασία την κυριαρχεί - μια ρουτίνα βεβαρημένη με helem κάνει αναζητήσεις σε hash· μια βεβαρημένη με padsv αγγίζει λεξιλογικές μεταβλητές· μια της οποίας ο χρόνος βρίσκεται στο subcall πληρώνει επιβάρυνση κλήσης που αξίζει να ενσωματωθεί.
Ο προφίλερ μετρά ό,τι εκτελεί ο διερμηνευτής. Κώδικας που το JIT μεταγλώττισε σε εγγενή κώδικα μηχανής δεν περνά από την αποστολή ops του διερμηνευτή, οπότε δεν εμφανίζεται op προς op. Ένας βρόχος που έχει εξαφανιστεί από τις χρονομετρήσεις ops είναι ένας βρόχος που ανέλαβε το JIT - που είναι η απάντηση που θέλατε. Για να προφιλάρετε τη συμπεριφορά του διερμηνευτή απομονωμένα, απενεργοποιήστε το JIT με --no-jit (δείτε παρακάτω).
Χρονομέτρηση ολόκληρου του προγράμματος: ο εκτελεστής benchmark#
Όταν το ερώτημα είναι «πόσο γρήγορο είναι το pperl σε αυτό, έναντι της συστημικής perl, με και χωρίς JIT και παραλληλισμό», ο εκτελεστής benchmark του έργου το απαντά απευθείας. Το 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 παρέχεται natively. Και τα δύο συμπεριφέρονται ακριβώς όπως περιμένει ένας προγραμματιστής 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 απ” ό,τι στο upstream: μια μικρο-σύγκριση Benchmark που τυλίγει τον κώδικα σε ένα sub { ... } μετρά τον κώδικα ως σώμα υπορουτίνας. Τα σώματα υπορουτινών δεν μεταγλωττίζονται με JIT - το JIT στοχεύει βρόχους, όχι πλαίσια κλήσης (δείτε Μεταγλώττιση JIT). Έτσι ένας σφιχτός αριθμητικός βρόχος που θα μεταγλωττιζόταν σε εμβέλεια αρχείου ενδέχεται να τρέξει ερμηνευμένος μέσα σε ένα callback Benchmark, και η σύγκριση υποτιμά την πραγματική ταχύτητα του βρόχου. Όταν αυτό που χρονομετράτε είναι ένας βρόχος που θα έπρεπε να περάσει από JIT, προτιμήστε το bench/run-perlbench ή ένα διάστημα Time::HiRes γύρω από τον βρόχο επιτόπου.
Απενεργοποίηση των βελτιστοποιητών για την απομόνωση μιας αιτίας#
Δύο σημαίες σάς επιτρέπουν να αποδώσετε μια επιτάχυνση - ή μια επιβράδυνση - στο σωστό στρώμα:
pperl --no-jit script.pl # interpreter only, no native compilation
pperl --no-parallel script.pl # single-threaded, no Rayon dispatch
Τρέξτε ένα πρόγραμμα με τρεις τρόπους - προεπιλογή, --no-jit, --no-parallel - και οι διαφορές σάς λένε ποιος βελτιστοποιητής κουβαλά την εργασία. Αν το --no-jit μετά βίας αλλάζει τον χρόνο, το JIT δεν εμπλεκόταν σε αυτόν τον κώδικα, και η μορφή του βρόχου είναι αυτό που πρέπει να εξετάσετε. Αν το --no-parallel μετά βίας τον αλλάζει, ο παραλληλοποιητής απέρριψε τον βρόχο - συνήθως λόγω μιας παρενέργειας που δεν μπόρεσε να αποκλείσει (δείτε Παράλληλη εκτέλεση).
Μια πειθαρχία μέτρησης#
Τρέξτε το πρόγραμμα μία φορά υπό την κατασκευή προφίλερ. Διαβάστε τις χρονομετρήσεις υπορουτινών. Οι κορυφαίες δύο ή τρεις γραμμές είναι ο προϋπολογισμός σας.
Αν μια θερμή ρουτίνα είναι ένας βρόχος που περιμένατε να μεταγλωττιστεί, επιβεβαιώστε με
run-perlbench +Jή χρονομετρώντας τον με--no-jitέναντι της προεπιλογής. Ένας βρόχος που δεν επιταχύνεται υπό το JIT είναι κακοσχηματισμένος, όχι αργός.Μόνο τώρα αλλάξτε κώδικα. Ξαναμετρήστε με τον ίδιο τρόπο. Κρατήστε την αλλαγή αν ο αριθμός κινήθηκε και το πρόγραμμα εξακολουθεί να παράγει την ίδια έξοδο.
Οι ισχυρισμοί περί απόδοσης είναι φθηνοί· η αναφορά του προφίλερ και ο λόγος του benchmark δεν είναι. Εμπιστευτείτε τη μέτρηση.
Δείτε επίσης#
Γράφοντας γρήγορο pperl - αφού βρείτε τον θερμό βρόχο, πώς να τον διαμορφώσετε ώστε να τον αναλάβουν το JIT και ο παραλληλοποιητής.
Ιδιωματισμοί - ποιες κλασικές βελτιστοποιήσεις αξίζει ακόμη να εφαρμόσετε προτού καταφύγετε στον προφίλερ.
Μεταγλώττιση JIT - τι μεταγλωττίζει το JIT και γιατί οι χρονομετρήσεις ερμηνευμένων ops εξαφανίζονται για τους μεταγλωττισμένους βρόχους.
Παράλληλη εκτέλεση - τι ασκούν η στήλη
+Pκαι η σημαία--no-parallel.Απόδοση regex - αν ο προφίλερ δείχνει σε ένα regexp, η αιτία και η θεραπεία βρίσκονται εκεί, όχι εδώ.