# Ιδιωματισμοί Η Perl κουβαλά δεκαετίες λαϊκής σοφίας περί απόδοσης - συνήθειες που απέδιδαν στον upstream διερμηνευτή και κληροδοτήθηκαν ως κανόνες. Μέρος αυτών των συμβουλών είναι διαχρονικό. Μέρος τους είναι πλέον ενεργά άσκοπο στο pperl, επειδή το JIT ή ο αναδιπλωτής χρόνου μεταγλώττισης κάνει ήδη τη δουλειά. Και λίγες νέες συνήθειες έχουν σημασία εδώ που κανένας upstream προγραμματιστής Perl δεν χρειάστηκε ποτέ. Αυτό το κεφάλαιο διαλέγει τους κλασικούς ιδιωματισμούς σε τρεις κάδους: - **Εξακολουθεί να ισχύει** - η συμβουλή κρατά· το κόστος που αποφεύγει είναι υπαρκτό και στο pperl. - **Κατέστη άνευ αντικειμένου** - ο μεταγλωττιστής ή το JIT του pperl εξαλείφει ήδη το κόστος, οπότε η περιστροφή δεν αποφέρει τίποτε και μόνο βλάπτει την αναγνωσιμότητα. - **Νέο** - σχετικό *λόγω* του τρόπου που μεταγλωττίζει το pperl, χωρίς upstream προηγούμενο. Μια γενική επιφύλαξη διατρέχει ολόκληρο τον κάδο «κατέστη άνευ αντικειμένου»: το JIT αναλαμβάνει έναν βρόχο μόνο όταν ο βρόχος είναι *διαμορφωμένος* γι” αυτό. Ένας βρόχος βεβαρημένος με κλήσεις υπορουτινών, αντιστοιχίες regexp ή χτίσιμο συμβολοσειρών δεν μεταγλωττίζεται και τρέχει στον διερμηνευτή - όπου η κλασική συμβουλή εφαρμόζεται ξανά πλήρως. Έτσι το «το JIT το χειρίζεται» ισχύει *για τους βρόχους που χειρίζεται το JIT*. Το [Γράφοντας γρήγορο pperl](writing-fast-pperl.md) είναι το συνοδευτικό κεφάλαιο για το πώς να εξασφαλίσετε ότι ο βρόχος σας είναι ένας από αυτούς. ## Εξακολουθεί να ισχύει Αυτά επιβιώνουν αμετάβλητα από τη μετάβαση στο pperl. Το κόστος που αποφεύγουν πληρώνεται στον διερμηνευτή, και ο περισσότερος θερμός κώδικας αγγίζει τον διερμηνευτή κάπου. - **Βγάλτε την αμετάβλητη εργασία έξω από τους βρόχους.** Ο υπολογισμός της ίδιας τιμής σε κάθε επανάληψη είναι σπαταλημένη εργασία είτε ο βρόχος είναι ερμηνευμένος είτε μεταγλωττισμένος. Το JIT δεν βγάζει έξω αυθαίρετες εκφράσεις για λογαριασμό σας· βγάλτε το αμετάβλητο έξω μόνοι σας. - **Αποφύγετε περιττά αντίγραφα μεγάλων δομών.** Το πέρασμα ενός μεγάλου πίνακα κατά τιμή, ή το τεμαχισμό του απλώς για μέτρηση, αντιγράφει. Περάστε μια αναφορά, χρησιμοποιήστε μια ψευδωνυμοποιημένη μεταβλητή βρόχου, ή εργαστείτε επιτόπου. Το μοντέλο τιμών του pperl έχει τα ίδια κόστη αντιγραφής που είχε πάντα η Perl. - **Προμεταγλωττίστε τα regexp που χρησιμοποιούνται σε έναν βρόχο με [`qr`](../../p5/core/perlfunc/qr.md).** Η μηχανή regex και η cache μεταγλώττισής της λειτουργούν όπως στο upstream· ένα μοτίβο που χτίζεται εκ νέου σε κάθε επανάληψη πληρώνει για επαναμεταγλώττιση. Αυτό είναι έδαφος regex - δείτε [απόδοση regex](../regular-expression/performance.md). - **Επιλέξτε τη σωστή δομή δεδομένων.** Μια αναζήτηση σε hash νικά μια γραμμική σάρωση στα ίδια μεγέθη όπως πάντα. Η αλγοριθμική πολυπλοκότητα δεν επηρεάζεται από κανένα runtime· ένας βρόχος O(n²) είναι αργός ακόμη και πλήρως JIT’d. - **Διαβάστε αρχεία σε κομμάτια σωστού μεγέθους.** Το γραμμή προς γραμμή έναντι του slurp-and-split είναι ζήτημα I/O, και το I/O δεν μεταγλωττίζεται με JIT. Οι συμβιβασμοί του upstream μεταφέρονται απευθείας. - **Προτιμήστε τις ενσωματωμένες λειτουργίες λίστας από τους χειρόγραφους βρόχους όπου η ενσωματωμένη είναι native.** Οι `sum`, `min`, `max`, `first` από το `List::Util` τρέχουν με ταχύτητα ενσωματωμένης συνάρτησης χωρίς στρώμα XS (δείτε [native αρθρώματα](../pperl-architecture/native-modules.md)). Ένας χειρόγραφος βρόχος συσσωρευτή *ενδέχεται* να φτάσει με JIT στην ίδια ταχύτητα, αλλά η native ενσωματωμένη σάς οδηγεί εκεί χωρίς να εξαρτάται από τη μορφή του βρόχου. ## Κατέστη άνευ αντικειμένου Αυτά ήταν πραγματικές νίκες στην upstream Perl. Στο pperl ο μεταγλωττιστής ή το JIT αφαιρεί ήδη το κόστος, οπότε η εφαρμογή του ιδιωματισμού με το χέρι δεν αποφέρει τίποτε και συνήθως κοστίζει σε σαφήνεια. ### Χειροκίνητη ενσωμάτωση θερμής αριθμητικής Η παλιά συνήθεια του ξετυλίγματος ενός σφιχτού αριθμητικού βρόχου, ή της χειροκίνητης ενσωμάτωσης ενός μικρού αριθμητικού βοηθού για να αποφευχθεί η επιβάρυνση κλήσης, στοχεύει ένα κόστος που το JIT αφαιρεί. Ένας αριθμητικός βρόχος `for` ή `while` πάνω σε ακέραιους ή κινητής υποδιαστολής συσσωρευτές μεταγλωττίζεται σε εγγενή κώδικα μηχανής, με τις μεταβλητές του βρόχου να κρατιούνται σε καταχωρητές CPU - δείτε [Μεταγλώττιση JIT](../pperl-architecture/jit.md). Το χειροκίνητο ξετύλιγμα ενός τέτοιου βρόχου τον κάνει μακρύτερο και δυσκολότερο στην ανάγνωση χωρίς να νικά τον μεταγλωττιστή. ```perl # Don't hand-unroll this. The JIT compiles the natural form to # native code with $i and $sum in registers. my $sum = 0; for my $i (1 .. 1_000_000) { $sum += $i * $i; } ``` Η περιστροφή αποδίδει μόνο αν ο βρόχος *δεν* περνά από JIT - και αν δεν περνά, η λύση είναι να τον κάνετε να περάσει από JIT (αφαιρέστε την κλήση ή τη λειτουργία συμβολοσειράς που τον απέκλεισε), όχι να τον ξετυλίξετε με το χέρι. ### Φύλακες καταγραφής με σταθερά DEBUG Ο ιδιωματισμός της προστασίας ακριβής καταγραφής πίσω από μια ψευδή σταθερά χρόνου μεταγλώττισης - ```perl use constant DEBUG => 0; # ... warn "state: @stuff\n" if DEBUG; ``` - δεν κοστίζει απολύτως τίποτε στο pperl, που είναι όλο το νόημα του ιδιωματισμού, και το pperl το προσφέρει πλήρως. Όταν η `DEBUG` είναι ψευδής σταθερά, ο μεταγλωττιστής αναδιπλώνει τον φύλακα `if DEBUG` κατά τη μεταγλώττιση: η απενεργοποιημένη πρόταση καταρρέει σε no-op, χωρίς έλεγχο συνθήκης και χωρίς διακλάδωση να απομένει στο μεταγλωττισμένο πρόγραμμα. Η παρεμβολή `"@stuff"` δεν χτίζεται ποτέ. Μπορείτε να αφήνετε φυλασσόμενη καταγραφή σε θερμά μονοπάτια με ήσυχη συνείδηση· μια ψευδής σταθερά `DEBUG` την σβήνει. Αυτός είναι ο ιδιωματισμός που λειτουργεί *όπως σχεδιάστηκε* - συνεχίστε να τον γράφετε. Άνευ αντικειμένου είναι κάθε περαιτέρω εξυπνάδα από πάνω: δεν χρειάζεται να σχολιάσετε ως ανενεργή την καταγραφή, να τη μετακινήσετε πίσω από μια sub, ή να την αφαιρέσετε για παραγωγή. Η σταθερά το κάνει. ### Αναδίπλωση σταθερών εκφράσεων μόνοι σας Ο προϋπολογισμός ενός σταθερού αριθμητικού αποτελέσματος, ή μιας σταθερής κλήσης σε μια καθαρή ενσωματωμένη, και η αποθήκευσή του σε μια μεταβλητή για να «αποφευχθεί ο επαναϋπολογισμός του» δεν αποφέρει τίποτε - ο μεταγλωττιστής αναδιπλώνει τις σταθερές εκφράσεις και τις καθαρές ενσωματωμένες με σταθερά ορίσματα (`sin`, `cos`, `sqrt`, `chr`, `ord`, και τα παρόμοια) κατά τη μεταγλώττιση, εφόσον η κλήση βρίσκεται σε έγκυρο πεδίο ορισμού. Τα `my $two_pi = 2 * 3.14159265358979;` και `my $root = sqrt(2);` υπολογίζονται μία φορά, κατά τη μεταγλώττιση, είτε «βοηθήσετε» βγάζοντάς τα έξω είτε όχι. Η μία λεπτή απόχρωση: η αναδίπλωση παραλείπεται για ορίσματα εκτός του πεδίου ορισμού μιας συνάρτησης (`sqrt` αρνητικού, `log` του μηδενός), οπότε αυτά διαφεύγουν στον χρόνο εκτέλεσης και κράζουν εκεί ακριβώς όπως πρέπει. Ποτέ δεν χρειάζεται να αναδιπλώσετε μια σταθερά με το χέρι για να κερδίσετε ταχύτητα. ### Αφαίρεση νεκρών βρόχων με σταθερή συνθήκη Ένα `while (0) { ... }` ή ένα μπλοκ που φυλάσσεται από μια σταθερά-ψευδή συνθήκη εξαλείφεται κατά τη μεταγλώττιση - το σώμα αφαιρείται, δεν παρακάμπτεται απλώς κατά τον χρόνο εκτέλεσης. (Ένα `for (; 0; ...)` σε στυλ C χειρίζεται ελαφρώς διαφορετικά: το σώμα δεν εισέρχεται ποτέ, αν και η op του σκελετού του βρόχου ενδέχεται να παραμείνει.) Κώδικας που μεταγλωττίζεται εκτός υπό συνθήκη πίσω από μια ψευδή σταθερά δεν φέρει βάρος κατά τον χρόνο εκτέλεσης· δεν χρειάζεται να τον διαγράψετε φυσικά για να γίνει το πρόγραμμα γρηγορότερο. ## Νέο Αυτά δεν έχουν upstream αντίστοιχο. Έχουν σημασία λόγω του τρόπου που το pperl μεταγλωττίζει και παραλληλοποιεί, και η εσφαλμένη εφαρμογή τους εκχωρεί αθόρυβα τις μεγαλύτερες επιταχύνσεις που προσφέρει το pperl. ### Κρατήστε τους θερμούς αριθμητικούς βρόχους αριθμητικούς Η μοναδική πολυτιμότερη νέα συνήθεια. Το JIT μεταγλωττίζει έναν αριθμητικό βρόχο και, όταν η παραλληλοποίηση είναι ενεργοποιημένη (η προεπιλογή) και ο βρόχος είναι μια καθαρή αναγωγή πάνω σε ένα γνωστό εύρος, τον αποστέλλει σε όλα τα νήματα - η διαφορά μεταξύ μιας επιτάχυνσης 76× και 431× στο benchmark Mandelbrot (δείτε [παράλληλη εκτέλεση](../concurrent-execution/parallel.md)). Μια λειτουργία συμβολοσειράς μέσα σε αυτόν τον βρόχο εκχωρεί το παράλληλο μισό. Όταν το JIT δει μια μεταβλητή συμβολοσειράς στον βρόχο - έναν στόχο συνένωσης `.=`, έναν συσσωρευτή συμβολοσειρών - υποβαθμίζει τον βρόχο σε *σειριακό* μεταγλωττισμένο βρόχο: παραμένει εγγενής κώδικας, αλλά δεν απλώνεται πλέον σε όλα τα νήματα. ```perl # Numeric reduction - JIT-compiled and eligible for parallel dispatch. my $total = 0; for my $i (0 .. $n) { $total += compute($i); # (only if compute() itself stays numeric/inlinable) } # A string built in the same loop demotes it to sequential JIT - # the parallel dispatch is gated off the moment a string var appears. my $log = ""; for my $i (0 .. $n) { $total += $i * $i; $log .= "$i "; # forfeits parallel dispatch for this loop } ``` Αν χρειάζεστε και την αριθμητική αναγωγή και τη συσσώρευση συμβολοσειρών, χωρίστε τες σε δύο βρόχους: ο αριθμητικός παραλληλοποιείται, ο συμβολοσειρών τρέχει ως σειριακός μεταγλωττισμένος βρόχος. Η ανάμειξή τους σάς κοστίζει την παράλληλη επιτάχυνση στην αριθμητική εργασία χωρίς κανένα κέρδος στην εργασία με συμβολοσειρές. ### Διαμορφώστε τις αναγωγές ως συσσώρευση-χωρίς-μηδενισμό Ο παραλληλοποιητής αναγνωρίζει μια μεταβλητή αναγωγής βλέποντάς την να συσσωρεύεται και να μη μηδενίζεται ποτέ μέσα στο σώμα του βρόχου (η ανάλυση αφαιρεί τα μοτίβα μηδενισμού από τα μοτίβα συσσώρευσης· μια μεταβλητή που είναι και τα δύο δεν αντιμετωπίζεται ως αναγωγή). Το να γράφετε τον συσσωρευτή σας στην απλή μορφή `$sum += ...`, δηλωμένο *πριν* τον βρόχο και ποτέ μη επαναρχικοποιημένο μέσα του, είναι αυτό που επιτρέπει στον βρόχο να διεκδικηθεί για παράλληλη εκτέλεση. ```perl my $sum = 0; # declared outside - a reduction for my $x (@data) { $sum += weight($x); # accumulate, never reset → parallelizable } ``` Η επαναρχικοποίηση του συσσωρευτή μέσα στον βρόχο, ή η παρεμβολή ενός μηδενισμού, ματαιώνει την αναγνώριση και ο βρόχος τρέχει σειριακά. Αυτό καλύπτεται πλήρως στην ενότητα [ανίχνευση αναγωγής](../concurrent-execution/parallel.md). ### Αφήστε τον βρόχο να περάσει από JIT - μην τον κρύβετε σε μια sub Το JIT μεταγλωττίζει *βρόχους*, όχι σώματα υπορουτινών. Ένας θερμός αριθμητικός βρόχος γραμμένος σε εμβέλεια αρχείου ή μέσα σε μια μεγαλύτερη ρουτίνα μεταγλωττίζεται μια χαρά· η ίδια αριθμητική αναδιαρθρωμένη ώστε κάθε επανάληψη να είναι μια κλήση υπορουτίνας όχι - το πλαίσιο κλήσης είναι έδαφος του διερμηνευτή, και η επιβάρυνση ανά κλήση είναι ακριβώς αυτό που το JIT δεν μπορεί να αφαιρέσει. Όταν ένας βρόχος είναι θερμός και αριθμητικός, κρατήστε το σώμα του ενσωματωμένο αντί να μεταφέρετε κάθε επανάληψη σε μια sub για τάξη. Αναδιαρθρώστε για σαφήνεια αλλού· κρατήστε τον εσωτερικό βρόχο επίπεδο. ## Δείτε επίσης - [Γράφοντας γρήγορο pperl](writing-fast-pperl.md) - το θετικό πρόγραμμα: πώς να διαμορφώσετε έναν βρόχο ώστε να τον αναλάβουν το JIT και ο παραλληλοποιητής. - [Μέτρηση](measuring.md) - επιβεβαιώστε ότι ένας ιδιωματισμός πράγματι κίνησε τον αριθμό πριν και μετά την εφαρμογή του. - [Μεταγλώττιση JIT](../pperl-architecture/jit.md) - τι μεταγλωττίζεται, γιατί η χειροκίνητη ενσωμάτωση αριθμητικής είναι περιττή. - [Παράλληλη εκτέλεση](../concurrent-execution/parallel.md) - η ανάλυση αναγωγής και η πύλη παρενεργειών πίσω από τους «νέους» ιδιωματισμούς. - [Ταξινόμηση](sorting.md) - οι ειδικοί για την ταξινόμηση ιδιωματισμοί (Schwartzian, Guttman-Rosler) αποκτούν το δικό τους κεφάλαιο.