Täna avaldame teise osa tõlkest materjalist, mis käsitleb, kuidas Dropbox korraldas miljonite ridade Python-koodi tüüpide kontrolli.
→
Ametlik tüüpide tugi (PEP 484)
Me tegime esimesed tõsised katsed mypy-ga Dropboxis 2014. aasta Hack Weeki ajal. Hack Week on Dropboxi üritus, mis toimub ühe nädala jooksul. Sellel ajal saavad töötajad töötada kõikide projektide kallal, mis neile huvi pakuvad! Paljud tuntud tehnoloogia projektid Dropboxis algasid just sellistel üritustel. Selle katse tulemusena jõudsime järeldusele, et mypy näeb välja paljutõotav, kuigi see projekt ei olnud veel laiemaks kasutamiseks valmis.
Sel ajal küpses õhus idee Python tüüpide näpunäidete andmise süsteemide standardiseerimise üle. Nagu ma juba ütlesin, alates Python 3.0-st sai funktsioonide tüüpide annotatsioonidega töötada, kuid need olid vaid meelevaldsed väljendid, ilma kindla süntaksita ja semantikat. Programmi käivitamisel ignoreeriti neid annotatsioone suuremasti lihtsalt. Pärast Hack Weeki alustasime standardiseerimise nimel töötamist. See töö viis PEPe 484 sünnini. (selle dokumendi koostamisel olid osalised Guido van Rossum, Lukáš Langa ja mina).
Meie motiive saab vaadelda kahel viisil. Esiteks loodame, et kogu Python'i ökosüsteem võiks omaks võtta ühise lähenemise tüübi vihjete (type hints — Python'is kasutatav termin, mis vastab mõistele 'tüübi annotatsioon') kasutamisele. Arvestades võimalikke riske, oleks see parem, kui kasutada palju üksteisega mittesobivaid lähenemisi. Teiseks soovisime avatud arutelu tüüpide annotatsiooni mehhanismide üle paljude Python'i kogukonna liikmetega. Osaliselt oli see soov tingitud vajadusest mitte näida 'hädas olevat' keele põhialustest laiemale Python'i programmeerijate massile. See on dünaamiliselt tüübitud keel, tuntud 'partide tüübistamise' poolest. Alguses ei saanud kogukonnas kahtlustesse suhtumise vältimine tüübi staatilise tüübistamise ideede osas. Kuid selline hoiak nõrgenes lõpuks — pärast seda, kui selgus, et staatilist tüübistamist ei plaanita kohustuslikuks muuta (ja pärast seda, kui inimesed mõistsid, et see on tegelikult kasulik).
Käivitatud süntaksitüübid olid lõpuks väga sarnased sellele, mida toona toetas mypy. PEP 484 dokument ilmus koos Python 3.5-ga 2015. aastal. Python ei olnud enam keel, mis toetab ainult dünaamilist tüüpimist. Mulle meeldib mõelda sellele sündmusele kui olulisele verstapostile Python'i ajaloos.
Migratsiooni alustamine
2015. aasta lõpus loodi Dropboxis mypy arendamiseks kolmest inimesest koosnev meeskond. Nendeks olid Guido van Rossum, Greg Price ja David Fisher. Sellest hetkest alates hakkas olukord kiirelt arenema. MyPy kasvuteel oli esimeseks takistuseks jõudlus. Nagu ma eelnevalt mainisin, mõtlesin projekti varajases arengufaasis mypy teostuse üleviimisele C keelde, kuid see mõte jäi sel hetkel kõrvale. Olime ummikseisus, kuna süsteemi käitamiseks kasutati CPython'i tõlgendajat, mis ei olnud piisavalt kiire selliste tööriistade jaoks nagu mypy. (Projekti PyPy, alternatiivne JIT-kompileerijaga Python'i teostus, ei aidanud ka meid.)
Kuid siin aitasid meid mõned algoritmilised täiustused. Esimeseks võimsaks "kiirendajaks" sai inkrementaalne kontroll. Selle täiustuse idee oli lihtne: kui kõik mooduli sõltuvused pärast eelmise mypy käivitamise hetke ei ole muutunud, siis saame sõltuvustega töötamisel kasutada andmeid, mis on eelmisel seansil vahemällu salvestatud. Me pidime vaid kontrollima tüüpe muudetud failides ja nendes failides, mis sõltusid neist. Mypy läks isegi natuke kaugemale: kui mooduli väline liides ei muutunud, siis pidas mypy õigeks, et teisi mooduleid, mis seda moodulit impordivad, ei pea uuesti kontrollima.
Inkremendiline kontroll aitas meil suure hulga olemasoleva koodi annotatsioonide lisamisel märkimisväärselt. Probleem on selles, et see protsess sisaldab tavaliselt mitmeid iteratiivseid mypy jooksutusi, kuna annotatsioone lisatakse järk-järgult koodile ja parendatakse järk-järgult. Esimene mypy jooks oli ikka veel väga aeglane, kuna selle käivitamisel tuli kontrollida palju sõltuvusi. Seetõttu rakendasime olukorra parandamiseks kaugkühveldustehnoloogia. Kui mypy tuvastab, et kohalik vahemälu on tõenäoliselt aegunud, laadib ta kogu koodibaasi praeguse vahemälu snapshot'i tsentraliseeritud hoidlast. Seejärel viib ta läbi inkremendilise kontrolli, kasutades seda snapshot'i. See tõi meid veel ühe suure sammu lähemale mypy jõudluse suurendamisele.
See oli kiire ja loomulik tüüpide kontrollimise süsteemi rakendamise periood Dropboxis. 2016. aasta lõpuks oli meil juba umbes 420 000 rida Python-koodi, millel olid tüüpide annotatsioonid. Paljud kasutajad olid tüüpide kontrollimise suhtes entusiastlikud. Dropboxis kasutas mypy järjest rohkem arendajameeskondi.
Kõik näis toona hästi, kuid meil oli veel palju tööd teha. Alustasime regulaarsete sisemiste kasutajauuringute läbiviimist, et tuvastada projekti probleemsed kohad ja mõista, milliseid küsimusi tuleb kõigepealt lahendada (seda praktikat kasutatakse ettevõttes ka täna). Selgus, et kaks ülesannet olid kõige olulisemad. Esimene – koodi katvuse suurendamine tüüpidega, teine – mypy kiirus. Oli täiesti selge, et meie töö mypy kiirendamise ja selle integreerimise kallal ettevõtte projektides oli veel kaugel lõppsaamisest. Täielikult teades nende kahe ülesande tähtsust, asusime neid lahendama.
Rohkem jõudlust!
Inkrementaalsed kontrollid kiirusid mypy'd, kuid see tööriist polnud ikka veel piisavalt kiire. Paljud inkrementaalsed kontrollid kestisid umbes minuti. Selle põhjuseks olid tsüklilised impordid. See ei üllata tõenäoliselt kedagi, kes on töötanud suurte Pythoniga kirjutatud koodibaasidega. Meil olid komplektid sadadest moodulitest, millest igaüks kaudselt importis kõik teised. Kui mõni fail tsüklite impordis muutus, pidi mypy töötlema kõik failid, mis kuulusid sellesse tsüklisse, ning sageli ka kõik moodulid, mis importisid mooduleid sellest tsüklist. Üks selline tsükkel oli kurikuulus „sõltuvuste kook”, mis põhjustas Dropboxis palju probleeme. Ühel hetkel sisaldas see struktuur sadu module, mida importisid, otse või kaudselt, mitmed testid, ning seda kasutati ka tootmiskoodis.
Me arutasime võimalust tsükliliste sõltuvuste "lahendamiseks", kuid meil polnud selleks ressursse. Koodi oli liiga palju, millega me ei olnud tuttavad. Lõppkokkuvõttes valisime alternatiivse lähenemise. Otsustasime, et mypy peaks ka probleemsete sõltuvuste korral kiiresti töötama. Me saavutasime selle eesmärgi mypy deemoniga. Deemon on serveriprotsess, mis pakub kahte huvitavat funktsiooni. Esiteks – ta hoiab kogu koodibaasi teavet mälus. See tähendab, et igal mypy käivitamisel ei pea laadima tuhandeid imporditud sõltuvustega seotud vahemälus olevaid andmeid. Teiseks – ta analüüsib sõltuvusi põhjalikult, väikeste struktuuriliste üksuste tasandil. Näiteks, kui funktsioon foo kutsub ellu funktsiooni bar, siis on sõltuvus foo alates bar. Kui fail muutub, siis demon töötleb alguses ainult muutunud faili isoleeritult. Seejärel vaatab ta selle faili muudatusi, mis on väljastpoolt nähtavad, näiteks muutunud funktsioonide allkirjat. Demon kasutab importide üksikasjalikku teavet täiendava kontrolli jaoks ainult nende funktsioonide kohta, mis tõeliselt kasutavad muudetud funktsiooni. Sellise lähenemisega tuleb tavaliselt kontrollida vaid mõnda funktsiooni.
Kogu selle elluviimine osutus keeruliseks, kuna algne mypy teostus oli tugevalt suunatud ühe faili kaupa töötlemisele. Me pidime silmitsi seisma paljude piirjuhtumitega, mille tekkimine nõudis korduvaid kontrollimisi, kui koodis midagi muudetud oli. Näiteks juhtub see siis, kui klassile määratakse uus põhi klass. Pärast seda, kui olime saavutanud soovitud tulemuse, suutsime suurendada enamikku inkrementsest kontrollist vaid mõne sekundi peale. Meie jaoks tundus see suur võit.
Veelgi rohkem jõudlust!
Koos eemaldatava vahemäluga, millest ma eespool räägin, lahendas mypy praktiliselt täielikult probleemid, mis tekkisid siis, kui programmeerija käivitas tüüpide kontrolli, muutes väikest hulka faile. Siiski jäi süsteemi jõudlus isegi kõige ebasoodsamas kasutusvariandis kaugele optimaalsest. Mypy puhas käivitamine võis võtta rohkem kui 15 minutit. Ja see oli palju rohkem, kui me oleksime soovinud. Igal nädalal olukord halvenes, kuna programmeerijad jätkasid uue koodi kirjutamist ja olemasoleva koodi annotatsioonide lisamist. Meie kasutajad ootasid endiselt suuremat jõudlust ja me olime hea meelega valmis neid toetama.
Otsustasime tagasi pöörduda ühe varasema idee juurde mypy kohta. Ehkki Cythoniga (see on süsteem, mis võimaldab tõlkida Pythonis kirjutatud koodi C-koodiks) tehtud eksperimendi käigus ei saavutanud me nähtavat kiiruskasvu, otsustasime elu sisse puhuda ideele luua oma kompilaator. Kuna mypy koodibaas (kirjutatud Pythonis) sisaldas juba kõiki vajalikke tüübi annotatsioone, tundus proovi tegemine nende annotatsioonide kasutamiseks süsteemi kiirusel põhinev. Loodud prototüüp näitas erinevates mikro-benchmärkides üle 10-kordset jõudluskasvu. Meie idee oli kompileerida Pythonimooduleid C-mooduliteks Cythoniga ja muuta tüübi annotatsioonid tüübi kontrollideks, mis teostatakse programmi käivitamise ajal (tüübi annotatsioone ignoreeritakse tavaliselt programmide käivitamisel ja neid kasutatakse ainult tüüpide kontrollimise süsteemide poolt). Me plaanisime tegelikult mypy rakenduse üleviimist Pythonist keelde, mis on statiliselt tüpiseeritud, mis näeks välja (ja enamasti ka töötaks) täpselt nagu Python. (See liik keeltevaheline migreerimine on saanud mypy projekti traditsiooniks. Algne mypy rakendus oli kirjutatud Alore'is, seejärel oli selles Java ja Python'i süntaktiline hübriid.)
CPythoni laienduste API orienteeritus oli võtmetähtsusega meie projekti haldamise võimekuse säilitamisel. Me ei pidanud rakendama virtuaalmasinat ega vajalikke raamatukogusid nagu mypy. Lisaks oleks kogu Python ökosüsteem endiselt kergesti ligipääsetav, koos kõigi tööriistadega (nt pytest). See võimaldas meil jätkata tõlgendatava Python koodi kasutamist arendustöös, mis andis meile võimaluse teha kiireid koodimuudatusi ja testimist, mitte oodata koodi kompileerimist. See tundus, nagu jõuaksime suurepäraselt tasakaalu hoidma, ja me nautisime seda.
Kompilaator, mille me nimetasime mypyc (sest front-endina kasutab ta tüüpide analüüsimiseks mypy), osutus üsna edukaks projektiks. Üldiselt saavutame me umbes 4-kordse kiiruskasvu sagedaste mypy käivitamiste juures ilma vahemälu kasutamata. Projekti mypyc arendamine võttis meie väikeselt meeskonnalt, kuhu kuulusid Michael Sullivan, Ivan Levkivski, Hugh Han ja mina, aega umbes 4 kalendrikuu. See töömaht oli tunduvalt väiksem, kui see oleks olnud mypy ümberkirjutamiseks näiteks C++-s või Go-s. Muudatusi, mida me projekti pidime tegema, oli samuti oluliselt vähem kui oleks pidanud tegema selle ümberkirjutamiseks teises keeles. Me lootsime ka, et saame viia mypyc tasemele, kus saavad seda kasutada ka teised Dropboxi arendajad oma koodi kompileerimiseks ja kiirusel kiirendamiseks.
Selle taseme jõudmiseks tuli meil rakendada mitmeid huvitavaid insenerilahendusi. Kompilaator suudab paljude toimingute täitmist kiirendada, kasutades kiireid madala taseme C-konstruktsioone. Näiteks tõlgitakse kompileeritud funktsiooni kutsumine C-funktsiooni kutsumiseks. Selliste väljakutsete täitmine on oluliselt kiirem kui tõlgendatud funktsiooni väljakutse. Teatud toimingud, nagu sõnaraamatutest otsimine, tuginesid endiselt tavapäraste C-API väljakutsete kasutamisele CPythonis, mis pärast kompileerimist osutusid vaid natuke kiiremateks. Oleme suutnud kõrvaldada tõlgendamise tekitatud täiendava koormuse, kuid see tõi antud juhul vaid väikese kasvu jõudluses.
Koodist kõige levinumate 'aeglike' operatsioonide tuvastamiseks tegime koodiprofiilimist. Olles saadud andmetega varustatud, üritasime kas nii 'häälestada' mypyc, et see genereeriks kiiremat C-koodi sarnaste operatsioonide jaoks, või kirjutada vastav Python-kood ümber, kasutades kiiremaid operatsioone (mõnikord ei olnud meil lihtsalt piisavalt lihtsat lahendust sellele või teisele probleemile). Python-koodi ümberkirjutamine osutus sageli kergemaks probleemilahenduseks kui sama transformatsiooni automaatne teostamine kompilaatoris. PIKAJAUGU plaanime paljusid neist transformatsioonidest automatiseerida, kuid tol hetkel keskendusime sellele, et minimaalsete pingutustega mypy’d kiirendada. Sel teel lõikasime mõned nurgad.
Jätkub…
Lugupidamisega lugejad! Milliseid muljeid jättis teid projekti mypy kohta, kui kuulsisite selle olemasolust?
Allikas: habr.com
