Pot do preverjanja tipov 4 milijonov vrstic Python kode. Del 2

Danas objavljujemo drugi deo prevoda materijala o tome kako je Dropbox organizovao kontrolu tipova nekoliko miliona redova Python koda.

Pot do preverjanja tipov 4 milijonov vrstic Python kode. Del 2

→ Pročitajte prvi deo

Zvanična podrška tipovima (PEP 484)

Sproveli smo prve ozbiljne eksperimente sa mypy u Dropbox-u tokom Hack Week-a 2014. Hack Week je događaj koji Dropbox organizuje tokom jedne nedelje. Tokom tog vremena, zaposleni mogu raditi na čemu god žele! Neki od najpoznatijih tehnoloških projekata Dropbox-a započeli su upravo na sličnim događajima. Kao rezultat ovog eksperimenta, zaključili smo da mypy izgleda obećavajuće, iako taj projekat još uvek nije bio spreman za široku upotrebu.

Tada je u vazduhu bila ideja o standardizaciji sistema za davanje tipova Python-a. Kao što sam već spomenuo, od Python-a 3.0 moglo je da se koristi anotacije tipova za funkcije, ali to su bile samo proizvoljne izraze, bez definisanog sintaktičkog i semantičkog značenja. Tokom izvršenja programa, te anotacije su uglavnom jednostavno ignorisane. Nakon Hack Week-a, počeli smo raditi na standardizaciji semantike. Ovaj rad doveo je do pojave PEP 484 (na ovom dokumentu radili su Guido van Rossum, Lukáš Lange i ja).

Naše motive je moguće posmatrati iz dva ugla. Prvo, nadali smo se da će cela Python ekosistem prihvatiti jedinstven pristup korišćenju tipova (type hints — termin koji se u Python-u koristi kao sinonim za 'anotacije tipova'). To bi, uz moguće rizike, bilo bolje od korišćenja mnogih međusobno nespojivih pristupa. Drugo, želeli smo otvoreno razgovarati o mehanizmima anotiranja tipova sa brojnim predstavnicima Python zajednice. Delimično, ova želja je bila vođena time da ne želimo da izgledamo kao 'odmetnici' od osnovnih ideja jezika u očima šireg kruga Python programera. Ovo je dinamički tipiziran jezik, poznat po 'patkogenom tipiziranju'. U zajednici, na početku, nije moglo da ne dođe do sumnje u ideju statičke tipizacije. No, takav stav je vremenom oslabio — nakon što je postalo jasno da statička tipizacija neće biti obavezna (i nakon što su ljudi shvatili da je zaista korisna).

Sprejeta sintaksa namigov po tipih je bila zelo podobna tisti, ki jo je v tistem času podpirala mypy. Dokument PEP 484 je izšel skupaj s Python 3.5 leta 2015. Python ni več bil jezik, ki bi podpiral le dinamično tipizacijo. Rad razmišljam o tem dogodku kot o pomembnem mejniku v zgodovini Pythona.

Začetek migracije

Konec leta 2015 je v Dropboxu, za delo na mypy, nastala ekipa iz treh ljudi. V njej so bili Guido van Rossum, Greg Price in David Fisher. Od takrat se je situacija začela hitro razvijati. Prva ovira na poti rasti mypy je bila zmogljivost. Kot sem že namignil zgoraj, sem v zgodnjem obdobju razvoja projekta razmišljal o prenosu implementacije mypy na jezik C, vendar je bila ta ideja zaenkrat izbrisana s seznama. Zastali smo na tem, da je za zagon sistema uporabljal interpreter CPython, ki ni bil dovolj hiter za orodja, kot je mypy. (Projekt PyPy, alternativna implementacija Pythona z JIT-kompilatorjem, nam tudi ni pomagal.)

Na srečo so nam tu pomagala nekatera algoritmična izboljšanja. Prvi močan "pospeševalec" je bila implementacija inkrementalne preveritve. Ideja tega izboljšanja je bila preprosta: če se vse odvisnosti modula od zadnjega zagona mypy niso spremenile, lahko med delom z odvisnostmi uporabimo podatke, shranjene med prejšnjo sejo. Potrebovali smo le izvajati preverjanje tipov v spremenjenih datotekah in v tistih datotekah, ki so bile od njih odvisne. Mypy je šel celo nekoliko dlje: če se zunanji vmesnik modula ni spreminjal – je mypy menil, da ni treba ponovno preverjati drugih modulov, ki uvažajo ta modul.

Inkrementna kontrola nam je znatno pomogla u anotaciji velikih količina postojećeg koda. Naime, ovaj proces obično uključuje mnoštvo iterativnih pokretanja mypy-a, jer se anotacije postepeno dodaju u kod i postepeno poboljšavaju. Prvo pokretanje mypy-a je i dalje bilo vrlo sporo, jer je trebalo proveriti veliki broj zavisnosti. Tada smo, radi poboljšanja situacije, implementirali mehanizam udaljenog keširanja. Ako mypy otkrije da je lokalni keš verovatno zastareo, preuzima trenutnu sliku keša za celu kodeks bazu iz centralizovanog repozitorijuma. Zatim izvršava, koristeći tu sliku, inkrementalnu proveru. Ovo nas je pomerilo još jedan značajan korak napred u povećanju performansi mypy-a.

To je bio period brze i prirodne implementacije sistema provere tipova u Dropbox-u. Do kraja 2016. godine, imali smo oko 420000 linija Python koda sa anotacijama tipova. Mnogi korisnici su entuzijastično pristupili proveri tipova. U Dropbox-u mypy je koristilo sve više timova programera.

Sve je tada izgledalo dobro, ali nas je još mnogo posla čekalo. Počeli smo da sprovodimo periodične interne ankete među korisnicima kako bismo identifikovali problematična mesta projekta i razumeli koja pitanja treba rešiti kao prioritet (ova praksa se koristi i danas). Kao što se pokazalo, najvažnije su bile dve zadatke. Prvo — potrebno je veće pokriće koda tipovima, drugo — mypy je trebao da radi brže. Bilo je sasvim jasno da je naš rad na ubrzanju mypy-a i njegovom implementaciji u projekte kompanije još daleko od završetka. Svesni važnosti ova dva zadatka, potpuno smo se posvetili njihovom rešavanju.

Više performansi!

Incremntalne provjere su ubrzale mypy, ali ovaj alat još uvijek nije bio dovoljno brz. Mnoge inkrementalne provjere trajale su oko minute. Uzrok tome bili su ciklični imports. To vjerojatno neće iznenaditi one koji su radili s velikim bazama koda pisanima na Pythonu. Imali smo skupove od stotina modula, od kojih je svaki neizravno uvozio sve druge. Ako bi se bilo koja datoteka u ciklusu uvoza promijenila, mypy je morao obraditi sve datoteke koje ulaze u taj ciklus, a često i sve module koji uvoze module iz ovog ciklusa. Jedan od takvih ciklusa bio je zloglasni 'klubak zavisnosti', koji je uzrokovao mnoge nevolje u Dropboxu. Jednom prilikom, ova struktura je sadržavala nekoliko stotina modula, pri čemu su je uvozili, izravno ili neizravno, mnogi testovi, a korištena je i u produkcionalnom kodu.

Razmatrali smo mogućnost 'raspetljavanja' cikličnih zavisnosti, ali nismo imali resurse za to. Bilo je previše koda s kojim nismo bili upoznati. Na kraju smo se odlučili za alternativni pristup. Odlučili smo napraviti da mypy radi brzo čak i u prisustvu 'klubaka zavisnosti'. Ovaj cilj postigli smo pomoću mypy demona. Demon je serverski proces koji implementira dvije zanimljive mogućnosti. Prvo, drži u memoriji informacije o cijeloj bazi koda. To znači da pri svakom pokretanju mypy ne mora učitavati keširane podatke koji se odnose na tisuće uvezenih zavisnosti. Drugo, pažljivo analizira zavisnosti između funkcija i drugih entiteta na razini malih strukturnih jedinica. Na primjer, ako funkcija foo вызывает функцию bar, tada postoji zavisnost foo od bar. Kada se datoteka promijeni — demon prvo, u izolaciji, obrađuje samo promijenjenu datoteku. Zatim gleda na promjene te datoteke, vidljive izvan, kao što su promijenjeni potpisi funkcija. Demon koristi detaljne informacije o uvozu samo za ponovno provjeravanje onih funkcija koje zaista koriste promijenjenu funkciju. Obično se ovim pristupom provjerava vrlo malo funkcija.

Izvedba vsega tega ni bila enostavna naloga, saj je bila prvotna izvedba mypy močno usmerjena na obdelavo enega samega datoteka naenkrat. Soočiti smo se morali z množico mejnih situacij, ki so zahtevale ponovne preveritve, kadar je prišlo do sprememb v kodi. Na primer, do tega pride, ko razredu dodelijo novo osnovno klas. Ko smo dosegli, kar smo želeli, smo večino inkrementalnih preverjanj uspeli skrajšati na nekaj sekund. To se nam je zdelo velik dosežek.

Še več zmogljivosti!

Skupaj z oddaljenim predpomnjenjem, o katerem sem govoril prej, je demon mypy praktično v celoti rešil težave, ki nastanejo, kadar programer pogosto izvaja preverjanje tipov ob spreminjanju majhnega števila datotek. Vendar je bila zmogljivost sistema v najslabšem možnem scenariju še vedno daleč od optimalne. Čisto zagon mypy je lahko trajal več kot 15 minut. Kar je bilo precej več, kot bi nam bilo sprejemljivo. Vsak teden se je situacija slabšala, saj so programerji še naprej pisali nov kodo in dodajali pripombe obstoječemu kodu. Naši uporabniki so še naprej hrepeneli po večji zmogljivosti, mi pa smo bili z veseljem pripravljeni priti naproti.

Odločili smo se vrniti k eni izmed prvih idej o mypy. Natančneje, k pretvorbi Python kode v C kodo. Eksperimenti s Cython-om (sistemom, ki omogoča prevod kode napisane v Pythonu v C kodo) niso prinesli opaznega pospeška, zato smo se odločili obuditi idejo pisanja lastnega prevajalnika. Ker je že obstoječa koda mypy (napisana v Pythonu) vsebovala vse potrebne oznake tipov, se nam je zdela smiselna poskusiti uporabiti te oznake za pospešitev delovanja sistema. Hitro sem ustvaril prototip za preverjanje te ideje. Pokaže je več kot 10-kratno povečanje zmogljivosti v različni mikro-benchmarkih. Naša ideja je bila, da prevedemo Python module v C module z uporabo Cython-a in spremenimo oznake tipov v preverjanja tipov, ki se izvajajo med izvajanjem programa (običajno se oznake tipov med izvajanjem programov ignorirajo in se uporabljajo le s sistemi za preverjanje tipov). Dejansko smo načrtovali, da prevedemo implementacijo mypy iz Pythona v jezik, ki bi bil statično tipiziran in bi izgledal (in večinoma tudi deloval) natančno tako kot Python. (Ta vrsta medjezikovne migracije je postala nekakšna tradicija projekta mypy. Prvotna implementacija mypy je bila napisana v Alore, nato je bil ustvarjen sintaktični hibrid Java in Python).

Usmeritev na API razširitev CPython je bila ključna za ohranjanje projektne upravljivosti. Nismo morali implementirati virtualnega stroja ali katerekoli knjižnice, ki bi jo mypy potreboval. Poleg tega bi bila celotna ekosistem Python še vedno dostopna, vse orodja (kot je pytest) bi bila na voljo. To je pomenilo, da bi lahko še naprej uporabljali interpretirano Python kodo med razvojem, kar bi nam omogočilo hitro spreminjanje kode in njeno testiranje, namesto da bi čakali na prevod kode. Videti je bilo, kot da nam odlično uspeva, da smo tako rekoč sedeli na dveh stolih, in to nam je bilo všeč.

Kompajler, ki smo ga poimenovali mypyc (saj uporablja mypy za analizo tipov kot sprednji del), se je izkazal za zelo uspešen projekt. Na splošno smo dosegli približno štirikratno pospešitev pogostih zagonov mypy brez uporabe predpomnjenja. Razvoj jedra projekta mypyc je manjšemu timu, ki so ga sestavljali Michael Sullivan, Ivan Levkivski, Hugh Han in jaz, vzel približno štiri koledarske mesece. Ta obseg dela je bil precej manjši od tistega, ki bi ga potrebovali za prepisovanje mypy, na primer v C++ ali Go. Poleg tega smo upali, da bomo lahko mypyc razvili do te mere, da ga bodo drugi programerji iz Dropboxa lahko uporabljali za kompilacijo in pospeševanje svojega kode.

Za dosego takšne ravni zmogljivosti smo morali uporabiti nekatere zanimive inženirske rešitve. Tako lahko kompajler pospeši izvajanje mnogih operacij s pomočjo hitrih nizkotohanovskih konstrukcij C. Na primer, klic skompajlirane funkcije se prevede v klic C-funkcije. Tak klic se izvaja precej hitreje kot klic interpretirane funkcije. Nekatere operacije, kot je iskanje v besedilnih datotekah, so se še vedno zanašale na običajne klice C-API iz CPython, ki so po kompilaciji postali le malenkost hitrejši. Uspešno smo se znebili dodatne obremenitve sistema, ki jo je povzročala interpretacija, a je to v tem primeru prineslo le majhen dobiček pri zmogljivosti.

Za odkrivanje najpogostejših 'počasnih' operacij smo izvedli profiliranje kode. Oboroženi s pridobljenimi podatki smo poskušali bodisi prilagoditi mypyc, da bi generiral hitrejšo C-kodo za tovrstne operacije, bodisi prepisati ustrezno Python kodo z uporabo hitrejših operacij (včasih pa nismo imeli preprostih rešitev za določene težave). Prepisovanje Python kode se je pogosto izkazalo za lažjo rešitev problema kot izvedba avtomatizacije iste transformacije v prevajalniku. Na dolgi rok smo želeli avtomatizirati mnoge od teh transformacij, vendar smo bili takrat osredotočeni na to, da bi mypy pospešili z minimalnim trudom. In na tej poti smo nekoliko skrajšali ovinke.

Nastaviće se…

Spoštovani bralci! Kakšni so bili vaši vtisi o projektu mypy, ko ste izvedeli za njegovo obstoj?

Pot do preverjanja tipov 4 milijonov vrstic Python kode. Del 2
Pot do preverjanja tipov 4 milijonov vrstic Python kode. Del 2

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster