Täna avaldame teise osa materjali tõlkest, kuidas Dropboxis korraldati tüübi kontrollimist mitme miljoni rea Python koodi kohta.
→
Ametlik tüübi tugi (PEP 484)
2014. aasta Hack Weeki ajal tegime Dropboxis esimesi tõsiseid katsetusi mypyga. Hack Week on üritus, mida korraldab Dropbox nädal aega, mil töötajad saavad töötada millegi kallal, mis neid huvitab! Paljud Dropboxi tuntud tehnoloogiaprojektid algasid just sellistel üritustel. Selle eksperimendi tulemusena jõudsime järeldusele, et mypy näeb välja lubav, kuigi see projekt polnud veel laiemaks kasutamiseks valmis.
Sel ajal valitses õhus idee Pythonitüüpide vihjete standardiseerimisest. Nagu ma varem mainisin, alates Python 3.0 oli võimalik kasutada funktsioonide tüübi annotateeringuid, kuid need olid lihtsalt suvalised väljendid, ilma kindla süntaksita ja semantikata. Programmeerimise käigus ignoreeriti neid annotateeringuid enamasti täielikult. Pärast Hack Weeki alustasime semantika standardiseerimise kallal töötamist. See töö viis PEP 484. (selle dokumendi kallal töötasid koos Guido van Rossum, Łukasz Langa ja mina).
Meie motiive võis vaadata kahest küljest. Esiteks lootsime, et kogu Python'i ökosüsteem võiks omaks võtta ühtse lähenemise tüübi vihjete kasutamisele (type hints — termin, mida Pythonis kasutatakse tüübi annotateeringute analoogina). See oleks, arvestades võimalikke riske, parem kui paljude üksteisega ühilduvate lähenemiste kasutamine. Teiseks soovisime avatud arutelu tüübi annotateerimise mehhanismide üle koos paljude Python'i kogukonna esindajatega. Osaliselt oli see soov tingitud hirmust, et me ei sooviks paista nagu „kätkijad” keele põhialuste ideede suhtes laiemas Python'i programmeerijate ringis. See on dünaamiliselt tüübitud keel, tuntud „partide tüübi” tõttu. Alguses võib kogukonnas olla mõned kahtlused staatilise tüübistamise idee suhtes. Kuid selline hoiak nõrgenes lõpuks — pärast seda, kui sai selgeks, et staatilist tüübistamist ei kavatseta muuta kohustuslikuks (ja pärast seda, kui inimesed mõistsid, et see on tõeliselt kasulik).
Lõpuks vastu võetud tüübi soovituste süntaks sarnanes väga sellele, mida tol ajal toetas mypy. PEP 484 dokument ilmus koos Python 3.5-ga 2015. aastal. Python ei olnud enam ainult dünaamilise tüpiseerimisega keel. Mulle meeldib mõelda sellele sündmusele kui olulisele verstapostile Python’i ajaloos.
Migratsiooni algus
2015. aasta lõpuks loodi Dropboxis mypy arendamiseks kolme inimesega meeskond. Sellesse kuulusid Guido van Rossum, Greg Price ja David Fisher. Sellest hetkest alates hakkas olukord kiiresti arenema. Esimene takistus mypy kasvu teel oli jõudlus. Nagu ma eelnevalt mainisin, mõtlesin projekti varases arengus mypy realiseerimise C keelde ümber tõlkida, kuid see idee tõmmati hetkel kõrvale. Me olime takerdunud olukorda, kus süsteemi käitamiseks kasutati CPython’i tõlgendajat, millel ei olnud piisavat kiirusetootlikkust mypy-sarnaste tööriistade jaoks. (PyPy projekt, alternatiivne Python’i realiseerimine JIT-kompilatsiooniga, ei aidanud ka meid.)
Õnneks aitasid meid mõned algoritmilised täiustused. Esimene võimas „kiirendaja” oli inkrementaalse kontrollimise realiseerimine. Selle täiustuse idee oli lihtne: kui kõiki sõltuvusi moodulist alates mypy eelmisest käivitamisest ei ole muudetud, saame sõltuvustega töötamisel kasutada eelmisest sessioonist vahemällu salvestatud andmeid. Meil oli vaja vaid kontrollida tüüpide muutunud failides ja nendes failides, mis sõltusid neist. Mypy läks isegi veidi kaugemale: kui mooduli väline liides ei muutunud, arvas mypy, et teisi mooduleid, mis seda moodulit impordivad, ei pea uuesti kontrollima.
Inkrementaalne kontroll aitas meid väga palju olemasoleva koodi suure mahtude annotatsioonide tegemisel. Asi on selles, et see protsess hõlmab tavaliselt mitmeid iteratiivseid mypy käivitusi, kuna annotatsioonid lisatakse järk-järgult koodi ja paranevad järk-järgult. Esimene mypy käivitus oli endiselt väga aeglane, kuna seda täites tuli kontrollida palju sõltuvusi. Seetõttu, olukorra parandamiseks, rakendasime kaugkasutuse vahehoidmismehhanismi. Kui mypy tuvastab, et kohalik vahemälu on tõenäoliselt aegunud, laadib ta kogu koodibaasi jaoks päevakohase vahemälu väljavõtte tsentraliseeritud registrist. Seejärel viib ta läbi inkrementaalse kontrolli, kasutades seda väljavõtet. See tõi meid veel ühe suure sammu võrra lähemale mypy tootlikkuse suurendamisele.
See oli kiire ja loomuliku tüübi kontrollimise süsteemi juurutamise periood Dropboxis. Aasta lõpuks 2016 oli meil juba umbes 420 000 rida Python-koodi, millel olid tüübiannotatsioonid. Paljud kasutajad suhtusid tüübikontrolli entusiasmiga. Dropboxis kasutas mypy järk-järgult üha rohkem arendustiime.
Kõik tundus siis hästi, kuid meil oli veel palju teha. Alustasime kasutajate regulaarsete siseuuringute läbiviimist, et tuvastada projekti probleemsed kohad ja mõista, millised küsimused tuleb esimesena lahendada (see praktika on ettevõttes kasutusel ka tänapäeval). Kaks ülesannet, mis olid selged, osutusid kõige olulisemaks. Esiteks, koodi tüüpide kattuvuse suurendamine, teiseks, tuli tagada, et mypy töötaks kiiremini. Oli täiesti selge, et meie töö mypy kiirendamise ja ettevõtte projektidesse juurutamise osas oli veel kaugel lõpuleviimisest. Täielikult teades nende kahe ülesande tähtsust, asusime nende lahendamise juurde.
Rohkem tootlikkust!
Inkrementaalsed kontrollid kiirendasid mypy'd, kuid see tööriist ei olnud endiselt piisavalt kiire. Paljud inkrementsed kontrollid kestsid umbes minute. Selle põhjuseks olid tsüklilised importimised. See ei üllata tõenäoliselt kedagi, kes on töötanud suurte Python'i koodibaasidega. Meil oli sadu mooduleid, millest igaüks importis kaudselt kõik teised. Kui ükski fail tsüklis muudeti, pidid mypy töötlema kõik failid, mis kuulusid sellesse tsüklisse, ja sageli ka kõik moodulid, mis importisid mooduleid sellest tsüklist. Üks neist tsüklitest oli tuntud kui
Me kaalume tsükliliste sõltuvuste "lahendamist", kuid meil polnud selleks ressursse. Koodi oli liiga palju, millega me polnud tuttavad. Lõpuks jõudsime alternatiivse lähenemiseni. Otsustasime muuta nii, et mypy töötaks kiiresti ka „sõlmedes”. Selle eesmärgini jõudsime mypy daemoniga. Daemon on serveriprotsess, mis rakendab kahte huvitavat funktsionaalsust. Esiteks, ta hoiab kogu koodibaasi teavet mälus. See tähendab, et iga mypy käivitamise korral ei ole vaja laadida vahemällu salvestatud andmeid, mis on seotud tuhandete imporditud sõltuvustega. Teiseks analüüsib ta hoolikalt, väikeste struktuuriüksuste tasandil, sõltuvusi funktsioonide ja teiste entiteetide vahel. Näiteks, kui funktsioon foo kutsub üles funktsiooni bar, on olemas sõltuvus foo alates bar. Kui fail muutub, töötleb daemon esmalt isoleeritult vaid muudetud faili. Seejärel vaatab ta, millised muudatused on sellest failist väljapoole nähtavad, nagu muudetud funktsioonide signatuurid. Daemon kasutab detailset teavet importide kohta vaid nende funktsioonide uutikk kontrollimiseks, mis tegelikult kasutatakse muudetud funktsiooni. Tavaliselt tuleb sellisel lähenemisel kontrollida vaid väga väheseid funktsioone.
Kogu selle rakendamine ei olnud lihtne, kuna mypy algne teostus oli tugevalt suunatud ühe faili töötlemisele korraga. Me pidime silmitsi seisma paljude piirsituatsioonidega, mille tekkimine nõudis korduvaid kontrollimisi juhtudel, kui koodis midagi muudeti. Näiteks juhtub see siis, kui klassile määratakse uus põhiklass. Pärast seda, kui olime teinud, mida soovisime, suutsime enamikku inkrementaalsetest kontrollidest läbiviimise aega vähendada vaid mõne sekundi peale. See tundus meile suure võiduna.
Veelgi suurem tootlikkus!
Koos eelpool mainitud eemaldusega MyPy vahemälust lahendas MyPy deemon praktiliselt täielikult probleemid, mis tekkisid, kui programmeerija käivitas tihti tüüpi kontrolle, tehes muudatusi ainult vähestes failides. Kuid süsteemi jõudlus selle kõige ebasoodsamas kasutusvariandis oli endiselt kaugel optimaalsest. MyPy puhas käivitamine võis kesta üle 15 minuti. See oli oluliselt rohkem, kui me rahuldada sooviksime. Igasse nädalasse muutus olukord üha hullemaks, kuna programmeerijad jätkasid uue koodi kirjutamist ja olemasolevale koodile annotatsioone lisamist. Meie kasutajad igatsesid endiselt suuremat tootlikkust ning olime hea meelega valmis neid toetama.
Otsustasime naasta ühe varasema idee juurde seoses mypy'ga. Nimelt — Python-koodi muutmine C-koodiks. Cythoni katsetused (see on süsteem, mis võimaldab tõlkida Pythonis kirjutatud koodi C-koodiks) ei andnud meile mingeid märgatavaid kiiruskasvuseid, seetõttu otsustasime elustada idee luua oma kompilaator. Kuna mypy koodibaas (mis on kirjutatud Pythonis) sisaldas juba kõiki vajalikke tüübiannotatsioone, tundus, et on väärt proovida kasutada neid annotatsioone süsteemi töö kiirendamiseks. Lõin kiiresti prototybi, et seda ideed kontrollida. See näitas erinevates mikro-benchmarkides üle 10-kordset jõudluse tõusu. Meie idee oli kompileerida Python-moduulid C-moduulideks Cythoni vahenditega ning muuta tüübiannotatsioonid tüüpide kontrollideks, mis teostatakse programmi käivitamise ajal (tüübiannotatsioone eiratakse tavaliselt programmi käivitamise ajal ja neid kasutatakse ainult tüüpide kontrollimise süsteemides). Tegelikult plaanisime viia mypy teostuse Pythonist keelde, mis oli loodud staatiliselt tüübid, mis näeks välja (ja enamasti töötaks) täpselt nagu Python. (See liik keeltest üksteisele migreerimist on muutunud mypy projekti traditsiooniks. Mypy algne teostus oli kirjutatud Alore'is, seejärel oli segu Java ja Pythoni süntaksist.)
CPythoni laienduste API suunamine oli võtmetegur, et mitte kaotada projektijuhtimise võimalusi. Me ei pidanud rakendama virtuaalmasinat ega ühtegi teeki, mida mypy vajas. Lisaks oleks meil endiselt juurdepääs kogu Python ökosüsteemile, kus oleksid kergesti saadaval kõik tööriistad (nt pytest). See tähendas, et saaksime arendamise käigus jätkata tõlgendatava Python-koodi kasutamist, mis võimaldaks meil töötada väga kiire koodimuutmise ja testimise skeemiga, mitte oodata koodi kompileerimist. See nägi välja nagu me saaksime suurepäraselt, nii öelda, kahel toolil istuda, ja meile meeldis see.
Kompilaator, mille me nimeks panime mypyc (kuna see kasutab tüüpide analüüsimiseks mypy front-endina), on osutunud väga edukaks projektiks. Üldiselt oleme saavutanud umbes neli korda kiirema mypy sagedaste käivituste ilma vahemäcachemisse. Mypyci projekti tuuma arendamine käsikäes väikese meeskonna, kuhu kuulusid Michael Sullivan, Ivan Levkivski, Hugh Han ja mina, võttis aega umbes neli kalendrikuud. See töömaht oli palju väiksem kui see, mis oleks vajalik mypy ümberkirjutamiseks näiteks C++-s või Go-s. Ja projekti muudatusi tuli teha palju vähem, kui oleks tulnud teha selle ümberkirjutamise korral teises keeles. Loodame samuti, et suudame viia mypyc sellisele tasemele, et Dropboxi teised programmeerijad saaksid seda kasutada oma koodi kompileerimiseks ja kiirusel.
Selle taseme jõudmiseks pidime rakendama mõningaid huvitavaid insenerilahendusi. Nii suudab kompilaator kiirendada paljude toimingute täitmist, kasutades C madala taseme konstruktsioone. Näiteks muudetakse kompileeritud funktsiooni kutse C-funktsiooni kutseks. Selline kutse toimub palju kiiremini kui tõlgendatud funktsiooni kutse. Mõned toimingud, nagu näiteks sõnastikes otsimine, tuginesid endiselt tavaliste C-API kutsete kasutamisele CPythonis, mis pärast kompileerimist osutusid vaid veidi kiiremateks. Õnnestus meil vabaneda tõlgendamisega seotud lisakoormusest süsteemile, kuid antud juhul andis see vaid väikese võidu jõudluses.
Aegade kõige levinumate „aeglaste“ operatsioonide tuvastamiseks viisime läbi koodiprofiilimise. Relvastatuna saadud andmetega üritasime kas «häälestada» mypyc, et see genereeriks selliste operatsioonide jaoks kiiremat C-koodi, või ümber kirjutada vastav Python-kood kiiremaks muutmiseks (mõnikord ei olnud meil lihtsalt piisavalt lihtsat lahendust mingi probleemiga tegelemiseks). Python-koodi ümberkirjutamine osutus sageli lihtsamaks probleemilahenduseks kui sama transformatsiooni automaatne täitmine kompilaatoris. Pikaajalises perspektiivis soovisime automatiseerida paljusid neist transformatsioonidest, kuid selles osas keskendusime, et võimalikult vähe pingutades kiirendada mypyd. Ja selle eesmärgi nimel lõikasime me nurki.
Jätkub…
Lugupeetud lugejad! Millised muljed tekkisid teile mypy projektist, kui said sellest teada?
Allikas: habr.com
