A strada per a verificazione di 4 milioni di linee di codice Python. Parte 2

Oghje publichemu a seconda parte di a traduzzione di materiale nantu à cumu Dropbox hà urganizatu u cuntrollu di tipu per parechji milioni di linee di codice Python.

A strada per a verificazione di 4 milioni di linee di codice Python. Parte 2

Leghjite a prima parte

Supportu di tipu ufficiale (PEP 484)

Avemu realizatu i nostri primi esperimenti serii cù mypy à Dropbox durante a Hack Week 2014. Hack Week hè un avvenimentu di una settimana ospitatu da Dropbox. Duranti stu tempu, l'impiegati ponu travaglià ciò chì volenu! Alcuni di i prughjetti tecnologichi più famosi di Dropbox sò cuminciati in avvenimenti cum'è questi. In u risultatu di stu esperimentu, avemu cunclusu chì mypy pari promettenti, ancu s'è u prugettu ùn hè ancu prontu per l'usu generalizatu.

À quellu tempu, l'idea di standardizà i sistemi di suggerimenti di tipu Python era in l'aria. Cumu l'aghju dettu, dapoi Python 3.0 era pussibule di utilizà annotazioni di tipu per e funzioni, ma queste eranu solu espressioni arbitrarie, senza sintassi è semantica definite. Durante l'esekzione di u prugramma, sti annotazioni eranu, per a maiò parte, simpliciamente ignorati. Dopu à Hack Week, avemu principiatu à travaglià nantu à a semantica di standardizazione. Stu travagliu hà purtatu à l'emergenza PEP 484 (Guido van Rossum, Łukasz Langa è aghju collaboratu à stu documentu).

I nostri mutivi puderanu esse vistu da dui lati. Prima, speremu chì tuttu l'ecosistema di Python puderia aduttà un accostu cumunu per utilizà suggerimenti di tipu (un termu utilizatu in Python cum'è l'equivalente di "annotazioni di tipu"). Questu, datu i risichi pussibuli, saria megliu cà l'utilizazione di parechji approcci mutuamente incompatibili. Siconda, vulemu discutiri apertamente i miccanismi di annotazione di tipu cù parechji membri di a cumunità Python. Stu desideriu era in parte dettatu da u fattu chì ùn vuleria micca vede cum'è "apostati" da l'idee basi di a lingua à l'ochji di a massa larga di i programatori Python. Hè una lingua digitata dinamicamente, cunnisciuta cum'è "duck typing". In a cumunità, à l'iniziu, una attitudine un pocu suspettu versu l'idea di typing staticu ùn pudia aiutà, ma nasce. Ma quellu sentimentu hà eventualmente calatu dopu chì hè diventatu chjaru chì a digitazione statica ùn era micca ubligatoriu (è dopu chì a ghjente hà capitu chì era veramente utile).

A sintassi di u suggerimentu di tipu chì hè stata eventualmente aduttata era assai simili à ciò chì mypy supportava à u mumentu. PEP 484 hè stata liberata cù Python 3.5 in 2015. Python ùn era più una lingua di scrittura dinamica solu. Mi piace à pensà à questu avvenimentu cum'è un passu impurtante in a storia di Python.

U principiu di a migrazione

À a fine di u 2015, Dropbox hà creatu una squadra di trè persone per travaglià in mypy. Inclusu Guido van Rossum, Greg Price è David Fisher. Da quellu mumentu, a situazione hà cuminciatu à sviluppà assai rapidamente. U primu ostaculu à a crescita di mypy era u rendiment. Cumu l'aghju insinuatu sopra, in i primi tempi di u prugettu aghju pensatu à traduzzione di l'implementazione mypy in C, ma sta idea hè stata cruciata da a lista per ora. Semu stati chjappi cù l'esecuzione di u sistema cù l'interprete CPython, chì ùn hè micca abbastanza veloce per arnesi cum'è mypy. (U prughjettu PyPy, una implementazione alternativa di Python cù un compilatore JIT, ùn ci hà ancu aiutatu).

Fortunatamente, alcuni miglioramenti algoritmichi sò ghjunti à u nostru aiutu quì. U primu "acceleratore" putente hè stata l'implementazione di a verificazione incrementale. L'idea daretu à sta migliione era simplice: se tutte e dipendenze di u modulu ùn anu micca cambiatu da a precedente run of mypy, allora pudemu usà e dati in cache durante a corsa precedente mentre travagliendu cù dipendenze. Avemu solu bisognu di fà una verificazione di tipu nantu à i schedarii mudificati è nantu à i schedarii chì dependevanu di elli. Mypy hà ancu andatu un pocu più: se l'interfaccia esterna di un modulu ùn hà micca cambiatu, mypy hà presumitu chì l'altri moduli chì anu impurtatu stu modulu ùn anu micca bisognu di verificà di novu.

A verificazione incrementale ci hà aiutatu assai quandu annotate una grande quantità di codice esistente. U puntu hè chì stu prucessu di solitu implica parechje corse iterative di mypy cum'è l'annotazioni sò gradualmente aghjuntu à u codice è migliurate gradualmente. A prima corsa di mypy era sempre assai lenta perchè avia assai dependenzii per verificà. Dopu, per migliurà a situazione, avemu implementatu un mecanismu di caching remoto. Se mypy detecta chì u cache lucale hè prubabilmente fora di data, scarica l'istantanea di cache attuale per tutta a basa di codice da u repositoriu centralizatu. Dopu eseguisce una verificazione incrementale utilizendu sta snapshot. Questu ci hà pigliatu un passu più grande per aumentà u rendiment di mypy.

Questu hè statu un periodu di adozione rapida è naturale di cuntrollu di tipu in Dropbox. À a fine di 2016, avemu digià avutu circa 420000 XNUMX linee di codice Python cù annotazioni di tipu. Parechji utilizatori eranu entusiasmu di u cuntrollu di tipu. Sempre più squadre di sviluppu usavanu Dropbox mypy.

Allora tuttu pareva bè, ma avemu sempre assai da fà. Avemu cuminciatu à realizà sondaggi internu periodichi di l'utilizatori per identificà e zone problematiche di u prugettu è capiscenu quali prublemi anu da esse risolti prima (sta pratica hè sempre usata in a cumpagnia oghje). I più impurtanti, cum'è diventatu chjaru, eranu dui compiti. Prima, avemu bisognu di più tipu di copertura di u codice, secondu, avemu bisognu di mypy per travaglià più veloce. Era assolutamente chjaru chì u nostru travagliu per accelerà mypy è implementà in i prughjetti di a cumpagnia era ancu luntanu da esse cumpletu. Noi, cunsapevoli di l'impurtanza di sti dui compiti, ci mettemu à risolve.

Più produtividade!

I cuntrolli incrementali anu fattu mypy più veloce, ma l'uttellu ùn era micca abbastanza veloce. Parechji cuntrolli incrementali durò circa un minutu. U mutivu di questu era l'impurtazioni cicliche. Questu prubabilmente ùn sorprenderà à nimu chì hà travagliatu cù grandi codebase scritti in Python. Avemu avutu insemi di centinaie di moduli, ognuna di quale hà impurtatu indirettu tutti l'altri. Se qualsiasi fugliale in un loop d'impurtazione hè statu cambiatu, mypy avia da processà tutti i schedari in quellu loop, è spessu qualsiasi moduli chì importavanu moduli da quellu loop. Un tali ciculu era u famusu "tangle di dipendenza" chì hà causatu assai prublemi in Dropbox. Una volta sta struttura cuntene parechji cintunari di moduli, mentri era impurtatu, direttamente o indirettu, assai teste, hè ancu usatu in u codice di produzzione.

Avemu cunsideratu a pussibilità di "untangling" dependencies circulari, ma ùn avemu micca e risorse per fà. Ci era troppu codice chì ùn eramu micca familiarizatu. In u risultatu, avemu ghjuntu cun un approcciu alternativu. Avemu decisu di fà u travagliu mypy rapidamente ancu in presenza di "tangles di dipendenza". Avemu ottinutu stu scopu usendu u daemon mypy. Un daemon hè un prucessu di u servitore chì implementa duie funzioni interessanti. Prima, guarda l'infurmazioni nantu à tutta a basa di codice in memoria. Questu significa chì ogni volta chì eseguite mypy, ùn avete micca a carica di dati in cache in relazione à millaie di dipendenze impurtate. Siconda, cun cura, à u livellu di unità strutturali chjuche, analizà e dependenzii trà e funzioni è altre entità. Per esempiu, se a funzione foo chjama una funzione bar, tandu ci hè una dependenza foo от bar. Quandu un schedariu cambia, u daemon prima, in isolamentu, processa solu u schedariu cambiatu. Dopu guarda i cambiamenti visibili esternamente à quellu schedariu, cum'è e signature di funzioni cambiate. U demoniu usa infurmazione dettagliata nantu à l'impurtazioni solu per verificà e funzioni chì in realtà utilizanu a funzione mudificata. Di genere, cù questu approcciu, avete da verificà assai pochi funzioni.

L'implementazione di tuttu questu ùn era micca faciule, postu chì l'implementazione originale di mypy era assai focu annantu à u processu di un schedariu à u mumentu. Avemu avutu à trattà cù parechje situazioni di cunfini, l'ocurrenza di quale esigeva cuntrolli ripetuti in i casi induve qualcosa cambiava in u codice. Per esempiu, questu succede quandu una classa hè assignata una nova classa di basa. Una volta avemu fattu ciò chì vuliamu, pudemu riduce u tempu d'esekzione di a maiò parte di i cuntrolli incrementali à pocu seconde. Questu ci paria una grande vittoria.

Ancu più produtividade!

Inseme cù a cache remota chì aghju discututu sopra, u demoniu mypy hà risoltu quasi cumplettamente i prublemi chì si presentanu quandu un programatore spessu eseguisce a verificazione di tipu, facendu cambiamenti à un picculu numeru di schedari. Tuttavia, u rendiment di u sistema in u casu d'usu menu favurevule era sempre luntanu da l'ottimali. Un startup pulitu di mypy puderia piglià più di 15 minuti. È questu era assai più di ciò chì avissimu avutu esse felici. Ogni simana, a situazione s'aggrava cum'è i programatori cuntinuavanu à scrive un novu codice è aghjunghjenu annotazioni à u codice esistenti. I nostri utilizatori eranu sempre affamati di più prestazioni, ma eramu felici di scuntràli à mità di strada.

Avemu decisu di vultà à una di l'idee precedenti riguardanti mypy. Vale à dì, per cunvertisce u codice Python in codice C. Sperimentà cù Cython (un sistema chì permette di traduce u codice scrittu in Python in codice C) ùn ci hà micca datu alcuna accelerazione visibile, cusì avemu decisu di rinviviscia l'idea di scrive u nostru propiu compilatore. Siccomu u codice mypy (scrittu in Python) cuntene digià tutte l'annotazioni di tipu necessariu, avemu pensatu chì valeria a pena di pruvà à utilizà sti annotazioni per accelerà u sistema. Aghju creatu rapidamente un prototipu per pruvà sta idea. Hà dimustratu un aumentu di più di 10 volte in u rendiment in diversi micro-benchmarks. A nostra idea era di cumpilà i moduli di Python à i moduli C cù Cython, è di trasfurmà l'annotazioni di tipu in cuntrolli di tipu di run-time (di solitu l'annotazioni di tipu sò ignorate in run-time è sò aduprate solu da i sistemi di cuntrollu di tipu). Avemu veramente prughjettatu di traduce l'implementazione di mypy da Python in una lingua chì hè stata cuncepita per esse scritta staticamente, chì parerebbe (è, per a maiò parte, travaglià) esattamente cum'è Python. (Stu tipu di migrazione incruciata hè diventata una tradizione di u prughjettu mypy. L'implementazione mypy originale hè stata scritta in Alore, allora ci era un hibridu sintatticu di Java è Python).

Fighjendu nantu à l'API di estensione CPython era chjave per ùn perde micca e capacità di gestione di u prughjettu. Ùn avemu micca bisognu di implementà una macchina virtuale o qualsiasi biblioteche chì mypy avia bisognu. Inoltre, averemu ancu accessu à tuttu l'ecosistema Python è tutti l'arnesi (cum'è pytest). Questu significava chì pudemu cuntinuà à aduprà u codice Python interpretatu durante u sviluppu, chì ci permette di cuntinuà à travaglià cù un mudellu assai veloce di fà cambiamenti di codice è di pruvà, invece di aspittà chì u codice per compilà. Paria chì faciamu un bellu travagliu di pusà nantu à duie sedie, per dì cusì, è ci hè piaciutu.

U compilatore, chì avemu chjamatu mypyc (perchè usa mypy cum'è front-end per analizà i tipi), hè diventatu un prughjettu assai successu. In generale, avemu ottinutu circa 4x accelerazione per frequenti mypy runs senza caching. U sviluppu di u core di u prughjettu mypyc hà pigliatu una piccula squadra di Michael Sullivan, Ivan Levkivsky, Hugh Hahn, è mè stessu circa 4 mesi di u calendariu. Questa quantità di travagliu era assai più chjuca di ciò chì avissi avutu bisognu di riscrive mypy, per esempiu, in C++ o Go. È avemu avutu à fà assai menu cambiamenti à u prughjettu di ciò ch'è no avissimu avutu da fà quandu si riscrive in un'altra lingua. Speremu ancu chì pudemu purtà mypyc à un tale livellu chì altri programatori Dropbox puderanu aduprà per compilà è accelerà u so codice.

Per ottene stu livellu di prestazione, avemu avutu à applicà alcune suluzioni di ingegneria interessanti. Cusì, u compilatore pò accelerà parechje operazioni utilizendu custruzzioni C veloci è di livellu bassu Per esempiu, una chjama di funzione cumpilata hè tradutta in una chjama di funzione C. È una tale chjamata hè assai più veloce di chjamà una funzione interpretata. Alcune operazioni, cum'è e ricerche di dizziunariu, anu sempre implicatu cù e chjama C-API regulari da CPython, chì eranu solu marginalmente più veloci quandu cumpilati. Pudemu eliminà a carica supplementu nantu à u sistema creatu da l'interpretazione, ma questu in questu casu hà datu solu un picculu guadagnu in termini di prestazioni.

Per identificà l'operazioni "lenti" più cumuni, avemu realizatu u prufilu di codice. Armati di sti dati, avemu pruvatu à tweak mypyc in modu chì generà un codice C più veloce per tali operazioni, o riscrivite u codice Python currispundente utilizendu operazioni più veloci (è qualchì volta simpricimenti ùn avemu micca solu solu solu solu solu per quellu o un altru prublema) . A riscrittura di u codice di Python era spessu una suluzione più faciule à u prublema chì avè u compilatore realizà automaticamente a stessa trasfurmazioni. À longu andà, vulemu automatizà parechje di sti trasfurmazioni, ma à u mumentu eramu focu annantu à accelerà mypy cù u minimu sforzu. È andendu versu questu scopu, avemu tagliatu parechji cantoni.

Per esse continuatu ...

Beni, lettori! Chì eranu e vostre impressioni di u prughjettu mypy quandu avete amparatu di a so esistenza?

A strada per a verificazione di 4 milioni di linee di codice Python. Parte 2
A strada per a verificazione di 4 milioni di linee di codice Python. Parte 2

Source: www.habr.com

Add a comment