Tee 4 miljoni Pythoni koodirea tüübikontrolliks. 2. osa

Täna avaldame materjali tõlke teise osa selle kohta, kuidas Dropbox korraldas mitme miljoni Pythoni koodirea tüübikontrolli.

Tee 4 miljoni Pythoni koodirea tüübikontrolliks. 2. osa

Lugege esimest osa

Ametlik tüübi tugi (PEP 484)

Esimesed tõsised katsed mypyga tegime Dropboxis Hack Week 2014 ajal. Hack Week on ühenädalane üritus, mida korraldab Dropbox. Selle aja jooksul saavad töötajad töötada, mida tahavad! Mõned Dropboxi kuulsaimad tehnoloogiaprojektid said alguse sellistel üritustel. Selle katse tulemusena jõudsime järeldusele, et mypy tundub paljulubav, kuigi projekt pole veel laialdaseks kasutamiseks valmis.

Sel ajal oli õhus idee Pythoni tüüpi vihjesüsteemide standardiseerimiseks. Nagu ma ütlesin, sai alates Python 3.0-st funktsioonide jaoks kasutada tüübimärkusi, kuid need olid lihtsalt suvalised avaldised, ilma määratletud süntaksi ja semantikata. Programmi täitmise ajal neid märkusi enamasti lihtsalt ignoreeriti. Pärast Hack Weeki alustasime semantika standardiseerimisega. See töö viis tekkimiseni PEP 484 (Guido van Rossum, Łukasz Langa ja mina tegime selle dokumendi koostamisel koostööd).

Meie motiive võis vaadata kahest küljest. Esiteks lootsime, et kogu Pythoni ökosüsteem suudab tüübivihjete kasutamisel kasutusele võtta ühise lähenemisviisi (Pythonis kasutatav termin "tüüpmärkuste" vastena). Arvestades võimalikke riske, oleks see parem kui paljude omavahel kokkusobimatute lähenemisviiside kasutamine. Teiseks tahtsime paljude Pythoni kogukonna liikmetega avalikult arutada tüübimärkuste mehhanisme. See soov oli osaliselt tingitud asjaolust, et me ei tahaks Pythoni programmeerijate laiade masside silmis näida keele põhiideedest "taganejatena". See on dünaamiliselt trükitav keel, mida tuntakse kui "pardi tippimist". Kogukonnas ei saanud alguses kuidagi kahtlane suhtumine staatilise tippimise ideesse aidata. Kuid see sentiment kahanes lõpuks pärast seda, kui sai selgeks, et staatiline tippimine ei ole kohustuslik (ja pärast seda, kui inimesed mõistsid, et see oli tegelikult kasulik).

Tüübi vihjete süntaks, mis lõpuks vastu võeti, oli väga sarnane sellele, mida mypy tol ajal toetas. PEP 484 ilmus Python 3.5-ga 2015. aastal. Python ei olnud enam dünaamiliselt trükitud keel. Mulle meeldib mõelda sellest sündmusest kui olulisest verstapostist Pythoni ajaloos.

Migratsiooni algus

2015. aasta lõpus lõi Dropbox kolmest inimesest koosneva meeskonna, kes töötaks mypy kallal. Nende hulka kuulusid Guido van Rossum, Greg Price ja David Fisher. Sellest hetkest alates hakkas olukord ülikiiresti arenema. Esimene takistus mypy kasvule oli jõudlus. Nagu ma ülalpool vihjasin, mõtlesin projekti algusaegadel tõlkida mypy-rakenduse C-keele, kuid see idee jäeti praeguseks nimekirjast maha. Jäime jänni süsteemi käitamisega CPythoni tõlgi abil, mis pole piisavalt kiire selliste tööriistade jaoks nagu mypy. (Meid ei aidanud ka PyPy projekt, alternatiivne Pythoni rakendus JIT-kompilaatoriga.)

Õnneks on meile siin appi tulnud mõned algoritmi täiustused. Esimene võimas "kiirend" oli järkjärgulise kontrolli rakendamine. Selle täiustuse idee oli lihtne: kui kõik mooduli sõltuvused pole pärast mypy eelmist käitamist muutunud, saame sõltuvustega töötamisel kasutada eelmise käitamise ajal vahemällu salvestatud andmeid. Meil tuli teha ainult muudetud failide ja nendest sõltuvate failide tüübikontroll. Mypy läks isegi veidi kaugemale: kui mooduli väline liides ei muutunud, eeldas mypy, et teisi mooduleid, mis selle mooduli importisid, ei ole vaja uuesti kontrollida.

Järkjärguline kontrollimine on meid palju aidanud suure hulga olemasoleva koodi märkimisel. Asi on selles, et see protsess hõlmab tavaliselt palju mypy iteratiivseid käike, kuna annotatsioonid lisatakse järk-järgult koodile ja neid täiustatakse järk-järgult. Mypy esimene käitamine oli ikka väga aeglane, kuna sellel oli palju sõltuvusi, mida kontrollida. Seejärel rakendasime olukorra parandamiseks kaugvahemällu salvestamise mehhanismi. Kui mypy tuvastab, et kohalik vahemälu on tõenäoliselt aegunud, laadib see tsentraliseeritud hoidlast alla kogu koodibaasi praeguse vahemälu hetktõmmise. Seejärel teostab see hetktõmmise abil järkjärgulise kontrolli. See on aidanud meid veel ühe suure sammu mypy jõudluse suurendamise suunas.

See oli Dropboxi tüübikontrolli kiire ja loomulik kasutuselevõtt. 2016. aasta lõpuks oli meil juba ligikaudu 420000 XNUMX Pythoni koodi rida koos tüübimärkustega. Paljud kasutajad olid tüübikontrollist entusiastlikud. Üha enam arendusmeeskondi kasutas Dropbox mypyt.

Siis tundus kõik hästi, aga meil oli veel palju teha. Hakkasime läbi viima perioodilisi ettevõttesiseseid kasutajauuringuid, et selgitada välja projekti probleemsed kohad ja mõista, millised probleemid vajavad esmalt lahendamist (selline praktika on ettevõttes kasutusel tänaseni). Olulisim, nagu selgus, olid kaks ülesannet. Esiteks vajasime koodi suuremat tüüpi katvust, teiseks oli vaja, et mypy töötaks kiiremini. Oli täiesti selge, et meie töö mypy kiirendamiseks ja selle juurutamiseks ettevõtte projektidesse ei olnud veel kaugeltki lõppenud. Meie, olles täiesti teadlikud nende kahe ülesande tähtsusest, asusime neid lahendama.

Rohkem tootlikkust!

Järkjärgulised kontrollid muutsid mypy kiiremaks, kuid tööriist ei olnud siiski piisavalt kiire. Paljud täiendavad kontrollid kestsid umbes minuti. Selle põhjuseks oli tsükliline import. See ei üllata ilmselt kedagi, kes on töötanud suurte Pythonis kirjutatud koodibaasidega. Meil oli sadade moodulite komplektid, millest igaüks importis kaudselt kõik teised. Kui imporditsüklis mõnda faili muudeti, pidi mypy töötlema kõiki selles tsüklis olevaid faile ja sageli ka kõiki mooduleid, mis importisid sellest tsüklist mooduleid. Üks selline tsükkel oli kurikuulus "sõltuvuste sasipundar", mis põhjustas Dropboxis palju probleeme. Kunagi sisaldas see struktuur mitusada moodulit, samal ajal kui seda imporditi otseselt või kaudselt palju teste, kasutati seda ka tootmiskoodis.

Kaalusime ringsõltuvuste "lahtiharutamise" võimalust, kuid meil polnud selleks ressursse. Seal oli liiga palju koodi, millega me polnud tuttavad. Selle tulemusena leidsime alternatiivse lähenemisviisi. Otsustasime panna mypy kiiresti tööle ka "sõltuvuspuntrate" olemasolul. Selle eesmärgi saavutasime mypy deemoni abil. Deemon on serveriprotsess, mis rakendab kahte huvitavat võimalust. Esiteks salvestab see mällu teabe kogu koodibaasi kohta. See tähendab, et iga kord, kui käivitate rakenduse mypy, ei pea te laadima vahemällu salvestatud andmeid, mis on seotud tuhandete imporditud sõltuvustega. Teiseks analüüsib ta hoolikalt, väikeste struktuuriüksuste tasandil funktsioonide ja muude üksuste vahelisi sõltuvusi. Näiteks kui funktsioon foo kutsub funktsiooni bar, siis tekib sõltuvus foo pärit bar. Kui fail muutub, töötleb deemon kõigepealt eraldiseisvalt ainult muudetud faili. Seejärel vaatab see selle faili väliselt nähtavaid muudatusi, näiteks muudetud funktsioonide allkirju. Deemon kasutab üksikasjalikku teavet impordi kohta ainult selleks, et kontrollida neid funktsioone, mis tegelikult kasutavad muudetud funktsiooni. Tavaliselt peate selle lähenemisviisi korral kontrollima väga vähe funktsioone.

Kõige selle rakendamine ei olnud lihtne, kuna algne mypy juurutus keskendus suuresti ühe faili korraga töötlemisele. Tegeleda tuli paljude piirsituatsioonidega, mille tekkimine nõudis korduvat kontrolli juhtudel, kui koodis midagi muutus. Näiteks juhtub see siis, kui klassile määratakse uus baasklass. Kui tegime seda, mida tahtsime, suutsime enamiku järkjärguliste kontrollide täitmise aega lühendada vaid mõne sekundini. See tundus meile suure võiduna.

Veelgi suurem tootlikkus!

Koos ülalpool käsitletud kaugvahemällu salvestamisega lahendas mypy deemon peaaegu täielikult probleemid, mis tekivad siis, kui programmeerija sageli tüübikontrolli käivitab, muutes väikese arvu faile. Süsteemi jõudlus kõige ebasoodsamal kasutusjuhul oli siiski optimaalsest kaugel. Mypy puhas käivitamine võib kesta üle 15 minuti. Ja see oli palju enamat, kui me oleksime rahul olnud. Iga nädalaga läks olukord hullemaks, kuna programmeerijad jätkasid uue koodi kirjutamist ja olemasolevale koodile märkuste lisamist. Meie kasutajad olid endiselt näljased suurema jõudluse järele, kuid meil oli hea meel nendega poolel teel kohtuda.

Otsustasime naasta ühe varasema idee juurde seoses mypyga. Nimelt Pythoni koodi teisendamiseks C koodiks. Katsetamine Cythoniga (süsteem, mis võimaldab tõlkida Pythonis kirjutatud koodi C-koodiks) ei andnud meile nähtavat kiirust, mistõttu otsustasime taaselustada idee kirjutada oma kompilaator. Kuna mypy koodibaas (kirjutatud Pythonis) sisaldas juba kõiki vajalikke tüübimärkusi, siis leidsime, et tasub proovida neid märkmeid süsteemi kiirendamiseks kasutada. Selle idee testimiseks lõin kiiresti prototüübi. See näitas erinevate mikro-etalonide puhul enam kui 10-kordset jõudluse kasvu. Meie idee oli kompileerida Pythoni moodulid Cythoni abil C-mooduliteks ja muuta tüübimärkused käitusaegseteks tüübikontrollideks (tavaliselt tüübimärkusi eiratakse käitamise ajal ja neid kasutavad ainult tüübikontrollisüsteemid ). Tegelikult plaanisime tõlkida mypy-rakenduse Pythonist keelde, mis oli mõeldud staatiliseks kirjutamiseks ja mis näeks välja (ja enamasti toimiks) täpselt nagu Python. (Sellisest keeleülesest migratsioonist on saanud omamoodi mypy projekti traditsioon. Algne mypy teostus on kirjutatud Alores, siis oli Java ja Pythoni süntaktiline hübriid).

CPythoni laienduse API-le keskendumine oli võtmetähtsusega, et mitte kaotada projektihaldusvõimalusi. Meil ei olnud vaja rakendada virtuaalmasinat ega teeke, mida mypy vajas. Lisaks oleks meil endiselt juurdepääs kogu Pythoni ökosüsteemile ja kõikidele tööriistadele (näiteks pytest). See tähendas, et saime arenduse ajal jätkata tõlgendatud Pythoni koodi kasutamist, võimaldades meil jätkata tööd väga kiire koodi muutmise ja testimise mustriga, selle asemel, et oodata koodi kompileerimist. Paistis, et me tegime nii-öelda kahel toolil istudes suurepärast tööd ja meile see meeldis.

Kompilaator, mida me nimetasime mypyciks (kuna see kasutab tüüpide analüüsimiseks esiotsana mypyt), osutus väga edukaks projektiks. Üldiselt saavutasime sagedaste mypy-käibete ilma vahemällu salvestamata kiiruse ligikaudu 4 korda. Mypyc-projekti tuuma väljatöötamine võttis väikesel meeskonnal Michael Sullivanist, Ivan Levkivskyst, Hugh Hahnist ja minust umbes 4 kalendrikuud. See töömaht oli palju väiksem, kui oleks olnud vaja mypy ümberkirjutamiseks näiteks C++ või Go keeles. Ja me pidime projektis tegema palju vähem muudatusi, kui oleksime pidanud seda mõnes teises keeles ümber kirjutades. Samuti lootsime, et suudame viia mypyci sellisele tasemele, et teised Dropboxi programmeerijad saaksid seda kasutada oma koodi koostamiseks ja kiirendamiseks.

Selle jõudlustaseme saavutamiseks pidime rakendama huvitavaid insenerilahendusi. Seega saab kompilaator kiirendada paljusid operatsioone, kasutades kiireid madala tasemega C-konstruktsioone. Näiteks kompileeritud funktsioonikutse tõlgitakse C-funktsiooni kutseks. Ja selline kõne on palju kiirem kui tõlgendatud funktsiooni kutsumine. Mõned toimingud, näiteks sõnastikuotsingud, hõlmasid endiselt CPythoni tavalisi C-API-kõnesid, mis olid kompileerimisel vaid veidi kiiremad. Suutsime küll kõrvaldada tõlgendusega tekitatud lisakoormuse süsteemile, kuid see andis antud juhul jõudluse osas vaid väikese võitu.

Levinumate “aeglaste” toimingute tuvastamiseks viisime läbi koodiprofiili. Nende andmetega relvastatud proovisime kas näpistada mypyci nii, et see genereeriks selliste toimingute jaoks kiirema C-koodi, või kirjutada vastava Pythoni koodi kiiremate toimingute abil ümber (ja mõnikord polnud meil lihtsalt selle või muu probleemi jaoks piisavalt lihtsat lahendust) . Pythoni koodi ümberkirjutamine oli probleemile sageli lihtsam lahendus kui lasta kompilaatoril sama teisendust automaatselt teha. Pikemas perspektiivis tahtsime paljusid neist teisendustest automatiseerida, kuid sel ajal keskendusime müpy kiirendamisele minimaalse pingutusega. Ja selle eesmärgi poole liikudes lõikasime mitu nurka.

Jätkub ...

Kallid lugejad! Millised muljed jäid teile müpyprojektist, kui selle olemasolust teada saite?

Tee 4 miljoni Pythoni koodirea tüübikontrolliks. 2. osa
Tee 4 miljoni Pythoni koodirea tüübikontrolliks. 2. osa

Allikas: www.habr.com

Lisa kommentaar