Η διαδρομή για τον έλεγχο πληκτρολόγησης 4 εκατομμυρίων γραμμών κώδικα Python. Μέρος 2

Σήμερα δημοσιεύουμε το δεύτερο μέρος της μετάφρασης υλικού σχετικά με το πώς το Dropbox οργάνωσε τον έλεγχο τύπων για πολλά εκατομμύρια γραμμές κώδικα Python.

Η διαδρομή για τον έλεγχο πληκτρολόγησης 4 εκατομμυρίων γραμμών κώδικα Python. Μέρος 2

Διαβάστε το πρώτο μέρος

Υποστήριξη επίσημου τύπου (PEP 484)

Πραγματοποιήσαμε τα πρώτα μας σοβαρά πειράματα με το mypy στο Dropbox κατά τη διάρκεια του Hack Week 2014. Το Hack Week είναι μια εκδήλωση μιας εβδομάδας που φιλοξενείται από το Dropbox. Σε αυτό το διάστημα, οι εργαζόμενοι μπορούν να δουλέψουν ό,τι θέλουν! Μερικά από τα πιο διάσημα τεχνολογικά έργα του Dropbox ξεκίνησαν σε εκδηλώσεις όπως αυτές. Ως αποτέλεσμα αυτού του πειράματος, καταλήξαμε στο συμπέρασμα ότι το mypy φαίνεται πολλά υποσχόμενο, αν και το έργο δεν είναι ακόμη έτοιμο για ευρεία χρήση.

Εκείνη την εποχή, η ιδέα της τυποποίησης των συστημάτων υπαινιγμού τύπου Python ήταν στον αέρα. Όπως είπα, από την Python 3.0 ήταν δυνατό να χρησιμοποιηθούν σχολιασμοί τύπων για συναρτήσεις, αλλά αυτές ήταν απλώς αυθαίρετες εκφράσεις, χωρίς καθορισμένη σύνταξη και σημασιολογία. Κατά την εκτέλεση του προγράμματος, αυτοί οι σχολιασμοί, ως επί το πλείστον, απλώς αγνοήθηκαν. Μετά το Hack Week, αρχίσαμε να εργαζόμαστε για την τυποποίηση της σημασιολογίας. Αυτό το έργο οδήγησε στην ανάδυση ΡΕΠ 484 (Ο Guido van Rossum, ο Łukasz Langa και εγώ συνεργαστήκαμε σε αυτό το έγγραφο).

Τα κίνητρά μας μπορούσαν να τα δούμε από δύο πλευρές. Πρώτον, ελπίζαμε ότι ολόκληρο το οικοσύστημα Python θα μπορούσε να υιοθετήσει μια κοινή προσέγγιση για τη χρήση υποδείξεων τύπων (ένας όρος που χρησιμοποιείται στην Python ως ισοδύναμος των "σχολιασμών τύπου"). Αυτό, δεδομένων των πιθανών κινδύνων, θα ήταν καλύτερο από τη χρήση πολλών αμοιβαία ασυμβίβαστων προσεγγίσεων. Δεύτερον, θέλαμε να συζητήσουμε ανοιχτά τους μηχανισμούς σχολιασμού τύπων με πολλά μέλη της κοινότητας Python. Αυτή η επιθυμία υπαγορεύτηκε εν μέρει από το γεγονός ότι δεν θα θέλαμε να μοιάζουμε με «αποστάτες» από τις βασικές ιδέες της γλώσσας στα μάτια των ευρειών μαζών των προγραμματιστών Python. Είναι μια δυναμικά δακτυλογραφημένη γλώσσα, γνωστή ως «δακτυλογράφηση πάπιας». Στην κοινότητα, στην αρχή, δεν θα μπορούσε παρά να προκύψει μια κάπως ύποπτη στάση απέναντι στην ιδέα της στατικής πληκτρολόγησης. Αλλά αυτό το συναίσθημα τελικά μειώθηκε αφού έγινε σαφές ότι η στατική πληκτρολόγηση δεν επρόκειτο να είναι υποχρεωτική (και αφού οι άνθρωποι συνειδητοποίησαν ότι ήταν πραγματικά χρήσιμο).

Η σύνταξη τύπου hint που τελικά υιοθετήθηκε ήταν πολύ παρόμοια με αυτή που υποστήριζε η mypy εκείνη την εποχή. Το PEP 484 κυκλοφόρησε με Python 3.5 το 2015. Η Python δεν ήταν πλέον μια δυναμικά πληκτρολογημένη γλώσσα. Μου αρέσει να σκέφτομαι αυτό το γεγονός ως ένα σημαντικό ορόσημο στην ιστορία της Python.

Έναρξη μετανάστευσης

Στα τέλη του 2015, το Dropbox δημιούργησε μια ομάδα τριών ατόμων για να εργαστεί στο mypy. Μεταξύ αυτών ήταν οι Guido van Rossum, Greg Price και David Fisher. Από εκείνη τη στιγμή, η κατάσταση άρχισε να εξελίσσεται εξαιρετικά γρήγορα. Το πρώτο εμπόδιο στην ανάπτυξη του mypy ήταν η απόδοση. Όπως υπαινίχθηκε παραπάνω, στις πρώτες μέρες του έργου σκέφτηκα να μεταφράσω την υλοποίηση του mypy σε C, αλλά αυτή η ιδέα διαγράφηκε από τη λίστα προς το παρόν. Είχαμε κολλήσει στην εκτέλεση του συστήματος χρησιμοποιώντας τον διερμηνέα CPython, ο οποίος δεν είναι αρκετά γρήγορος για εργαλεία όπως το mypy. (Το έργο PyPy, μια εναλλακτική υλοποίηση Python με μεταγλωττιστή JIT, δεν μας βοήθησε επίσης.)

Ευτυχώς, κάποιες αλγοριθμικές βελτιώσεις μας βοήθησαν εδώ. Ο πρώτος ισχυρός «επιταχυντής» ήταν η εφαρμογή του σταδιακού ελέγχου. Η ιδέα πίσω από αυτήν τη βελτίωση ήταν απλή: εάν όλες οι εξαρτήσεις της λειτουργικής μονάδας δεν έχουν αλλάξει από την προηγούμενη εκτέλεση του mypy, τότε μπορούμε να χρησιμοποιήσουμε τα δεδομένα που αποθηκεύτηκαν στην κρυφή μνήμη κατά την προηγούμενη εκτέλεση ενώ εργαζόμαστε με εξαρτήσεις. Χρειαζόταν μόνο να κάνουμε έλεγχο τύπου στα τροποποιημένα αρχεία και στα αρχεία που εξαρτώνται από αυτά. Το Mypy προχώρησε ακόμη και λίγο παραπέρα: εάν η εξωτερική διεπαφή μιας λειτουργικής μονάδας δεν άλλαζε, η mypy υπέθεσε ότι άλλες λειτουργικές μονάδες που εισήγαγαν αυτήν τη λειτουργική μονάδα δεν χρειαζόταν να ελεγχθούν ξανά.

Ο σταδιακός έλεγχος μας έχει βοηθήσει πολύ όταν σχολιάζουμε μεγάλες ποσότητες υπάρχοντος κώδικα. Το θέμα είναι ότι αυτή η διαδικασία συνήθως περιλαμβάνει πολλές επαναληπτικές εκτελέσεις του mypy καθώς οι σχολιασμοί προστίθενται σταδιακά στον κώδικα και βελτιώνονται σταδιακά. Η πρώτη εκτέλεση του mypy ήταν ακόμα πολύ αργή επειδή είχε πολλές εξαρτήσεις για έλεγχο. Στη συνέχεια, για να βελτιώσουμε την κατάσταση, εφαρμόσαμε έναν μηχανισμό απομακρυσμένης προσωρινής αποθήκευσης. Εάν το mypy εντοπίσει ότι η τοπική κρυφή μνήμη είναι πιθανό να είναι ξεπερασμένη, κατεβάζει το τρέχον στιγμιότυπο προσωρινής μνήμης για ολόκληρη τη βάση κώδικα από το κεντρικό αποθετήριο. Στη συνέχεια, εκτελεί έναν σταδιακό έλεγχο χρησιμοποιώντας αυτό το στιγμιότυπο. Αυτό μας έκανε ένα ακόμη μεγάλο βήμα προς την αύξηση της απόδοσης του mypy.

Αυτή ήταν μια περίοδος γρήγορης και φυσικής υιοθέτησης του ελέγχου τύπου στο Dropbox. Μέχρι το τέλος του 2016, είχαμε ήδη περίπου 420000 γραμμές κώδικα Python με σχολιασμούς τύπου. Πολλοί χρήστες ήταν ενθουσιασμένοι με τον έλεγχο τύπου. Όλο και περισσότερες ομάδες ανάπτυξης χρησιμοποιούσαν το Dropbox mypy.

Όλα φαίνονταν καλά τότε, αλλά είχαμε ακόμα πολλά να κάνουμε. Αρχίσαμε να πραγματοποιούμε περιοδικές έρευνες εσωτερικών χρηστών για να εντοπίσουμε προβληματικές περιοχές του έργου και να κατανοήσουμε ποια ζητήματα πρέπει να επιλυθούν πρώτα (αυτή η πρακτική εξακολουθεί να χρησιμοποιείται στην εταιρεία σήμερα). Τα πιο σημαντικά, όπως έγινε σαφές, ήταν δύο καθήκοντα. Πρώτον, χρειαζόμασταν περισσότερη κάλυψη τύπου του κώδικα, δεύτερον, χρειαζόμασταν το mypy για να λειτουργήσει πιο γρήγορα. Ήταν απολύτως σαφές ότι το έργο μας για να επιταχύνουμε το mypy και να το εφαρμόσουμε σε εταιρικά έργα απείχε ακόμη πολύ από το να ολοκληρωθεί. Εμείς, έχοντας πλήρη επίγνωση της σημασίας αυτών των δύο εργασιών, ξεκινήσαμε την επίλυσή τους.

Περισσότερη παραγωγικότητα!

Οι επαυξητικοί έλεγχοι έκαναν το mypy πιο γρήγορο, αλλά το εργαλείο δεν ήταν ακόμα αρκετά γρήγορο. Πολλοί επαυξητικοί έλεγχοι διήρκεσαν περίπου ένα λεπτό. Ο λόγος για αυτό ήταν οι κυκλικές εισαγωγές. Αυτό πιθανότατα δεν θα εκπλήξει κανέναν που έχει εργαστεί με μεγάλες βάσεις κώδικα γραμμένες σε Python. Είχαμε σετ από εκατοντάδες ενότητες, καθεμία από τις οποίες εισήγαγε έμμεσα όλες τις άλλες. Εάν άλλαζε οποιοδήποτε αρχείο σε έναν βρόχο εισαγωγής, το mypy έπρεπε να επεξεργαστεί όλα τα αρχεία σε αυτόν τον βρόχο και συχνά τυχόν λειτουργικές μονάδες που εισήγαγαν λειτουργικές μονάδες από αυτόν τον βρόχο. Ένας τέτοιος κύκλος ήταν το περιβόητο «κουβάρι εξάρτησης» που προκάλεσε πολλά προβλήματα στο Dropbox. Μόλις αυτή η δομή περιείχε αρκετές εκατοντάδες ενότητες, ενώ εισήχθη, άμεσα ή έμμεσα, πολλές δοκιμές, χρησιμοποιήθηκε και στον κώδικα παραγωγής.

Εξετάσαμε το ενδεχόμενο να «ξεμπλέξουμε» τις κυκλικές εξαρτήσεις, αλλά δεν είχαμε τα μέσα να το κάνουμε. Υπήρχε πάρα πολύς κώδικας με τον οποίο δεν γνωρίζαμε. Ως αποτέλεσμα, καταλήξαμε σε μια εναλλακτική προσέγγιση. Αποφασίσαμε να κάνουμε το mypy να λειτουργήσει γρήγορα ακόμα και με την παρουσία «μπλεγμάτων εξάρτησης». Πετύχαμε αυτόν τον στόχο χρησιμοποιώντας τον δαίμονα mypy. Ένας δαίμονας είναι μια διαδικασία διακομιστή που υλοποιεί δύο ενδιαφέροντα χαρακτηριστικά. Πρώτον, αποθηκεύει πληροφορίες για ολόκληρη τη βάση κώδικα στη μνήμη. Αυτό σημαίνει ότι κάθε φορά που εκτελείτε το mypy, δεν χρειάζεται να φορτώνετε αποθηκευμένα δεδομένα που σχετίζονται με χιλιάδες εισαγόμενες εξαρτήσεις. Δεύτερον, αναλύει προσεκτικά, σε επίπεδο μικρών δομικών μονάδων, τις εξαρτήσεις μεταξύ λειτουργιών και άλλων οντοτήτων. Για παράδειγμα, εάν η συνάρτηση foo καλεί μια συνάρτηση bar, τότε υπάρχει μια εξάρτηση foo από bar. Όταν αλλάζει ένα αρχείο, ο δαίμονας πρώτα, μεμονωμένα, επεξεργάζεται μόνο το αλλαγμένο αρχείο. Στη συνέχεια εξετάζει εξωτερικά ορατές αλλαγές σε αυτό το αρχείο, όπως αλλαγμένες υπογραφές συναρτήσεων. Ο δαίμονας χρησιμοποιεί λεπτομερείς πληροφορίες σχετικά με τις εισαγωγές μόνο για να ελέγξει δύο φορές τις συναρτήσεις που χρησιμοποιούν πραγματικά την τροποποιημένη συνάρτηση. Συνήθως, με αυτήν την προσέγγιση, πρέπει να ελέγξετε πολύ λίγες λειτουργίες.

Η εφαρμογή όλων αυτών δεν ήταν εύκολη, καθώς η αρχική εφαρμογή mypy επικεντρώθηκε σε μεγάλο βαθμό στην επεξεργασία ενός αρχείου τη φορά. Είχαμε να αντιμετωπίσουμε πολλές οριακές καταστάσεις, η εμφάνιση των οποίων απαιτούσε επαναλαμβανόμενους ελέγχους σε περιπτώσεις που κάτι άλλαζε στον κώδικα. Για παράδειγμα, αυτό συμβαίνει όταν σε μια κλάση εκχωρείται μια νέα βασική κλάση. Μόλις κάναμε αυτό που θέλαμε, μπορέσαμε να μειώσουμε τον χρόνο εκτέλεσης των περισσότερων σταδιακών ελέγχων σε λίγα μόνο δευτερόλεπτα. Αυτό μας φάνηκε σαν μια μεγάλη νίκη.

Ακόμα μεγαλύτερη παραγωγικότητα!

Μαζί με την απομακρυσμένη προσωρινή αποθήκευση που ανέφερα παραπάνω, ο δαίμονας mypy έλυσε σχεδόν πλήρως τα προβλήματα που προκύπτουν όταν ένας προγραμματιστής εκτελεί συχνά έλεγχο τύπου, κάνοντας αλλαγές σε μικρό αριθμό αρχείων. Ωστόσο, η απόδοση του συστήματος στη λιγότερο ευνοϊκή περίπτωση χρήσης απείχε ακόμη πολύ από τη βέλτιστη. Μια καθαρή εκκίνηση του mypy μπορεί να διαρκέσει πάνω από 15 λεπτά. Και αυτό ήταν πολύ περισσότερο από ό,τι θα ήμασταν ευχαριστημένοι. Κάθε εβδομάδα η κατάσταση γινόταν χειρότερη καθώς οι προγραμματιστές συνέχιζαν να γράφουν νέο κώδικα και να προσθέτουν σχολιασμούς στον υπάρχοντα κώδικα. Οι χρήστες μας ήταν ακόμα πεινασμένοι για περισσότερη απόδοση, αλλά ήμασταν ευτυχείς που τους συναντήσαμε στα μισά του δρόμου.

Αποφασίσαμε να επιστρέψουμε σε μία από τις προηγούμενες ιδέες σχετικά με το mypy. Δηλαδή, για να μετατρέψετε τον κώδικα Python σε κώδικα C. Ο πειραματισμός με το Cython (ένα σύστημα που σας επιτρέπει να μεταφράζετε κώδικα γραμμένο σε Python σε κώδικα C) δεν μας έδωσε ορατή επιτάχυνση, έτσι αποφασίσαμε να αναβιώσουμε την ιδέα ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​Δεδομένου ότι η βάση κώδικα mypy (γραμμένη σε Python) περιείχε ήδη όλους τους απαραίτητους σχολιασμούς τύπου, σκεφτήκαμε ότι θα άξιζε τον κόπο να προσπαθήσουμε να χρησιμοποιήσουμε αυτούς τους σχολιασμούς για να επιταχύνουμε το σύστημα. Δημιούργησα γρήγορα ένα πρωτότυπο για να δοκιμάσω αυτήν την ιδέα. Έδειξε πάνω από 10 φορές αύξηση της απόδοσης σε διάφορα μικρο-σημεία αναφοράς. Η ιδέα μας ήταν να μεταγλωττίσουμε τις λειτουργικές μονάδες Python σε μονάδες C χρησιμοποιώντας το Cython και να μετατρέψουμε τους σχολιασμούς τύπου σε ελέγχους τύπου χρόνου εκτέλεσης (συνήθως οι σχολιασμοί τύπων αγνοούνται κατά την εκτέλεση και χρησιμοποιούνται μόνο από συστήματα ελέγχου τύπων). Στην πραγματικότητα σχεδιάζαμε να μεταφράσουμε την υλοποίηση mypy από την Python σε μια γλώσσα που είχε σχεδιαστεί για να πληκτρολογείται στατικά, που θα έμοιαζε (και, ως επί το πλείστον, θα λειτουργούσε) ακριβώς όπως η Python. (Αυτό το είδος της διαγλωσσικής μετανάστευσης έχει γίνει κάτι σαν παράδοση του έργου mypy. Η αρχική υλοποίηση του mypy γράφτηκε στο Alore, μετά υπήρχε ένα συντακτικό υβρίδιο Java και Python).

Η εστίαση στο API επέκτασης CPython ήταν το κλειδί για να μην χαθούν οι δυνατότητες διαχείρισης έργου. Δεν χρειαζόταν να εφαρμόσουμε μια εικονική μηχανή ή βιβλιοθήκες που χρειαζόταν η mypy. Επιπλέον, θα εξακολουθούσαμε να έχουμε πρόσβαση σε ολόκληρο το οικοσύστημα της Python και σε όλα τα εργαλεία (όπως το pytest). Αυτό σήμαινε ότι μπορούσαμε να συνεχίσουμε να χρησιμοποιούμε ερμηνευμένο κώδικα Python κατά την ανάπτυξη, επιτρέποντάς μας να συνεχίσουμε να εργαζόμαστε με ένα πολύ γρήγορο μοτίβο αλλαγών κώδικα και δοκιμής του, αντί να περιμένουμε τη μεταγλώττιση του κώδικα. Φαινόταν ότι κάναμε εξαιρετική δουλειά καθισμένοι σε δύο καρέκλες, ας πούμε έτσι, και μας άρεσε.

Ο μεταγλωττιστής, τον οποίο ονομάσαμε mypyc (καθώς χρησιμοποιεί το mypy ως front-end για την ανάλυση τύπων), αποδείχθηκε ένα πολύ επιτυχημένο έργο. Συνολικά, επιτύχαμε περίπου 4 φορές επιτάχυνση για συχνές εκτελέσεις mypy χωρίς προσωρινή αποθήκευση. Για την ανάπτυξη του πυρήνα του έργου mypyc χρειάστηκε μια μικρή ομάδα από τους Michael Sullivan, Ivan Levkivsky, Hugh Hahn και εμένα περίπου 4 ημερολογιακούς μήνες. Αυτός ο όγκος εργασίας ήταν πολύ μικρότερος από ό,τι θα χρειαζόταν για την επανεγγραφή του mypy, για παράδειγμα, σε C++ ή Go. Και έπρεπε να κάνουμε πολύ λιγότερες αλλαγές στο έργο από όσες θα έπρεπε να κάνουμε όταν το ξαναγράφαμε σε άλλη γλώσσα. Ελπίζαμε επίσης ότι θα μπορούσαμε να φέρουμε το mypyc σε τέτοιο επίπεδο ώστε άλλοι προγραμματιστές του Dropbox να το χρησιμοποιήσουν για να μεταγλωττίσουν και να επιταχύνουν τον κώδικά τους.

Για να επιτύχουμε αυτό το επίπεδο απόδοσης, έπρεπε να εφαρμόσουμε μερικές ενδιαφέρουσες λύσεις μηχανικής. Έτσι, ο μεταγλωττιστής μπορεί να επιταχύνει πολλές λειτουργίες χρησιμοποιώντας γρήγορες, χαμηλού επιπέδου κατασκευές C. Για παράδειγμα, μια κλήση μεταγλωττισμένης συνάρτησης μεταφράζεται σε κλήση συνάρτησης C. Και μια τέτοια κλήση είναι πολύ πιο γρήγορη από την κλήση μιας ερμηνευμένης συνάρτησης. Ορισμένες λειτουργίες, όπως οι αναζητήσεις λεξικών, εξακολουθούσαν να περιλαμβάνουν τη χρήση κανονικών κλήσεων C-API από το CPython, οι οποίες ήταν οριακά πιο γρήγορες κατά τη μεταγλώττιση. Καταφέραμε να εξαλείψουμε το πρόσθετο φορτίο στο σύστημα που δημιουργήθηκε από την ερμηνεία, αλλά αυτό σε αυτή την περίπτωση έδωσε μόνο ένα μικρό κέρδος όσον αφορά την απόδοση.

Για να εντοπίσουμε τις πιο συνηθισμένες «αργές» λειτουργίες, πραγματοποιήσαμε δημιουργία προφίλ κώδικα. Οπλισμένοι με αυτά τα δεδομένα, προσπαθήσαμε είτε να τροποποιήσουμε το mypyc ώστε να δημιουργεί ταχύτερο κώδικα C για τέτοιες λειτουργίες είτε να ξαναγράψουμε τον αντίστοιχο κώδικα Python χρησιμοποιώντας ταχύτερες λειτουργίες (και μερικές φορές απλώς δεν είχαμε μια αρκετά απλή λύση για αυτό ή άλλο πρόβλημα) . Η επανεγγραφή του κώδικα Python ήταν συχνά μια ευκολότερη λύση στο πρόβλημα από το να εκτελεί αυτόματα ο μεταγλωττιστής τον ίδιο μετασχηματισμό. Μακροπρόθεσμα, θέλαμε να αυτοματοποιήσουμε πολλούς από αυτούς τους μετασχηματισμούς, αλλά εκείνη τη στιγμή επικεντρωθήκαμε στην επιτάχυνση του mypy με ελάχιστη προσπάθεια. Και προχωρώντας προς αυτόν τον στόχο, κόψαμε αρκετές γωνίες.

Για να συνεχιστεί ...

Αγαπητοί αναγνώστες! Ποιες ήταν οι εντυπώσεις σας από το έργο mypy όταν μάθατε για την ύπαρξή του;

Η διαδρομή για τον έλεγχο πληκτρολόγησης 4 εκατομμυρίων γραμμών κώδικα Python. Μέρος 2
Η διαδρομή για τον έλεγχο πληκτρολόγησης 4 εκατομμυρίων γραμμών κώδικα Python. Μέρος 2

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο