# Μέτρηση Δεν μπορείτε να ρυθμίσετε ό,τι δεν έχετε μετρήσει, και στο 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`: ```bash cargo build --release --features=profile PPERL_PROFILE=1 ./target/release/pperl script.pl ``` Όταν το πρόγραμμα τερματίζει - ή με `SIGINT` / `SIGTERM`, ώστε ένα μακροχρόνιο ή κολλημένο πρόγραμμα να μπορεί ακόμη να προφιλαριστεί διακόπτοντάς το - η αναφορά τυπώνεται στο `STDERR`. Έχει δύο ενότητες. **Χρονομετρήσεις υπορουτινών**, οι κορυφαίες καταχωρίσεις ανά συνολικό χρόνο: ```default -- 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**, οι κορυφαίες καταχωρίσεις ανά συνολικό χρόνο, που κατονομάζουν τη λειτουργία του διερμηνευτή αντί για μια γραμμή του πηγαίου σας κώδικα: ```default -- 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 σε έως τέσσερις διαμορφώσεις. ```bash ./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](writing-fast-pperl.md) εξηγεί γιατί και τι να αλλάξετε. ## Εργαλεία αμιγούς Perl που λειτουργούν: Benchmark και Time::HiRes Τα κλασικά εργαλεία μέτρησης εντός προγράμματος είναι αμιγής Perl. Τρέχουν στο pperl μέσω του κανονικού μονοπατιού αρθρωμάτων - το `Benchmark` είναι προσβάσιμο στο τυπικό μονοπάτι ενσωμάτωσης και εκτελείται ως Perl στον διερμηνευτή του pperl· το `Time::HiRes` παρέχεται natively. Και τα δύο συμπεριφέρονται ακριβώς όπως περιμένει ένας προγραμματιστής Perl. Το `Benchmark` για A/B σύγκριση δύο τρόπων γραφής της ίδιας ρουτίνας: ```perl 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` για περιστασιακή χρονομέτρηση πραγματικού χρόνου ενός μεμονωμένου μπλοκ: ```perl 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](../pperl-architecture/jit.md)). Έτσι ένας σφιχτός αριθμητικός βρόχος που θα μεταγλωττιζόταν σε εμβέλεια αρχείου ενδέχεται να τρέξει ερμηνευμένος μέσα σε ένα callback `Benchmark`, και η σύγκριση υποτιμά την πραγματική ταχύτητα του βρόχου. Όταν αυτό που χρονομετράτε είναι ένας βρόχος που θα έπρεπε να περάσει από JIT, προτιμήστε το `bench/run-perlbench` ή ένα διάστημα `Time::HiRes` γύρω από τον βρόχο επιτόπου. ## Απενεργοποίηση των βελτιστοποιητών για την απομόνωση μιας αιτίας Δύο σημαίες σάς επιτρέπουν να αποδώσετε μια επιτάχυνση - ή μια επιβράδυνση - στο σωστό στρώμα: ```bash 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` μετά βίας τον αλλάζει, ο παραλληλοποιητής απέρριψε τον βρόχο - συνήθως λόγω μιας παρενέργειας που δεν μπόρεσε να αποκλείσει (δείτε [Παράλληλη εκτέλεση](../concurrent-execution/parallel.md)). ## Μια πειθαρχία μέτρησης 1. Τρέξτε το πρόγραμμα μία φορά υπό την κατασκευή προφίλερ. Διαβάστε τις χρονομετρήσεις υπορουτινών. Οι κορυφαίες δύο ή τρεις γραμμές είναι ο προϋπολογισμός σας. 2. Αν μια θερμή ρουτίνα είναι ένας βρόχος που περιμένατε να μεταγλωττιστεί, επιβεβαιώστε με `run-perlbench +J` ή χρονομετρώντας τον με `--no-jit` έναντι της προεπιλογής. Ένας βρόχος που δεν επιταχύνεται υπό το JIT είναι κακοσχηματισμένος, όχι αργός. 3. Μόνο τώρα αλλάξτε κώδικα. Ξαναμετρήστε με τον ίδιο τρόπο. Κρατήστε την αλλαγή αν ο αριθμός κινήθηκε και το πρόγραμμα εξακολουθεί να παράγει την ίδια έξοδο. Οι ισχυρισμοί περί απόδοσης είναι φθηνοί· η αναφορά του προφίλερ και ο λόγος του benchmark δεν είναι. Εμπιστευτείτε τη μέτρηση. ## Δείτε επίσης - [Γράφοντας γρήγορο pperl](writing-fast-pperl.md) - αφού βρείτε τον θερμό βρόχο, πώς να τον διαμορφώσετε ώστε να τον αναλάβουν το JIT και ο παραλληλοποιητής. - [Ιδιωματισμοί](idioms.md) - ποιες κλασικές βελτιστοποιήσεις αξίζει ακόμη να εφαρμόσετε προτού καταφύγετε στον προφίλερ. - [Μεταγλώττιση JIT](../pperl-architecture/jit.md) - τι μεταγλωττίζει το JIT και γιατί οι χρονομετρήσεις ερμηνευμένων ops εξαφανίζονται για τους μεταγλωττισμένους βρόχους. - [Παράλληλη εκτέλεση](../concurrent-execution/parallel.md) - τι ασκούν η στήλη `+P` και η σημαία `--no-parallel`. - [Απόδοση regex](../regular-expression/performance.md) - αν ο προφίλερ δείχνει σε ένα regexp, η αιτία και η θεραπεία βρίσκονται εκεί, όχι εδώ.