Polku 4 miljoonan Python-koodirivin tyyppitarkistukseen. Osa 2

Julkaisemme tänään toisen osan materiaalin käännöksestä siitä, kuinka Dropbox järjesti useiden miljoonien Python-koodirivien tyyppihallinnan.

Polku 4 miljoonan Python-koodirivin tyyppitarkistukseen. Osa 2

Lue osa yksi

Virallinen tyyppituki (PEP 484)

Teimme ensimmäiset vakavat kokeilumme mypyn kanssa Dropboxissa Hack Week 2014 -tapahtumassa. Hack Week on Dropboxin isännöimä viikon mittainen tapahtuma. Tänä aikana työntekijät voivat tehdä mitä haluavat! Jotkut Dropboxin tunnetuimmista teknologiaprojekteista alkoivat tällaisissa tapahtumissa. Tämän kokeilun tuloksena päätimme, että mypy näyttää lupaavalta, vaikka projekti ei ole vielä valmis laajaan käyttöön.

Tuolloin ilmassa oli ajatus Python-tyyppisten vihjejärjestelmien standardoinnista. Kuten sanoin, Python 3.0:sta lähtien oli mahdollista käyttää funktioiden tyyppimerkintöjä, mutta nämä olivat vain mielivaltaisia ​​lausekkeita, ilman määriteltyä syntaksia ja semantiikkaa. Ohjelman suorituksen aikana nämä merkinnät jätettiin suurimmaksi osaksi huomiotta. Hack Weekin jälkeen aloimme työstää semantiikan standardointia. Tämä työ johti syntymiseen PEP 484 (Guido van Rossum, Łukasz Langa ja minä teimme yhteistyötä tässä asiakirjassa).

Motiivejamme voitiin tarkastella kahdelta puolelta. Ensinnäkin toivoimme, että koko Python-ekosysteemi voisi omaksua yhteisen lähestymistavan tyyppivihjeiden käyttämiseen (Pythonissa käytetty termi "tyyppimerkintöjen" vastineena). Tämä olisi mahdolliset riskit huomioon ottaen parempi vaihtoehto kuin käyttää monia keskenään yhteensopimattomia lähestymistapoja. Toiseksi halusimme keskustella avoimesti tyyppimerkintämekanismeista monien Python-yhteisön jäsenten kanssa. Tämä halu johtui osittain siitä, että emme halua näyttää kielen perusajatuksista ”luopijoilta” laajojen Python-ohjelmoijien silmissä. Se on dynaamisesti kirjoitettu kieli, joka tunnetaan nimellä "ankkakirjoitus". Yhteisössä heti alussa hieman epäluuloinen asenne staattisen kirjoittamisen ajatukseen ei voinut muuta kuin syntyä. Mutta tämä tunne lopulta hiipui, kun kävi selväksi, että staattinen kirjoittaminen ei ollut pakollista (ja sen jälkeen, kun ihmiset ymmärsivät, että se oli todella hyödyllistä).

Tyyppivihjesyntaksi, joka lopulta otettiin käyttöön, oli hyvin samanlainen kuin mitä mypy tuki tuolloin. PEP 484 julkaistiin Python 3.5:n kanssa vuonna 2015. Python ei ollut enää dynaamisesti kirjoitettu kieli. Pidän tätä tapahtumaa merkittävänä virstanpylväänä Pythonin historiassa.

Siirron alku

Vuoden 2015 lopussa Dropbox loi kolmen hengen tiimin työskentelemään mypyn parissa. Heihin kuuluivat Guido van Rossum, Greg Price ja David Fisher. Siitä hetkestä lähtien tilanne alkoi kehittyä erittäin nopeasti. Ensimmäinen este mypyn kasvulle oli suorituskyky. Kuten yllä vihjasin, ajattelin projektin alkuaikoina kääntää mypy-toteutuksen C-kielelle, mutta tämä idea jätettiin toistaiseksi pois listalta. Olimme jumissa järjestelmän käyttämisessä CPython-tulkin avulla, joka ei ole tarpeeksi nopea mypyn kaltaisille työkaluille. (PyPy-projekti, vaihtoehtoinen Python-toteutus JIT-kääntäjällä, ei myöskään auttanut meitä.)

Onneksi joitain algoritmisia parannuksia on tullut avuksemme täällä. Ensimmäinen tehokas "kiihdytin" oli asteittaisen tarkistuksen käyttöönotto. Tämän parannuksen idea oli yksinkertainen: jos kaikki moduulin riippuvuudet eivät ole muuttuneet mypyn edellisen ajon jälkeen, voimme käyttää edellisen ajon aikana välimuistissa olevia tietoja työskennellessämme riippuvuuksien kanssa. Meidän piti vain suorittaa tyyppitarkistus muokatuille tiedostoille ja tiedostoille, jotka riippuivat niistä. Mypy meni jopa hieman pidemmälle: jos moduulin ulkoinen käyttöliittymä ei muuttunut, mypy oletti, että muita tämän moduulin tuoneita moduuleja ei tarvinnut tarkistaa uudelleen.

Inkrementaalinen tarkistus on auttanut meitä paljon, kun merkitään suuria määriä olemassa olevaa koodia. Asia on siinä, että tämä prosessi sisältää yleensä monia iteratiivisia mypy-ajoja, kun merkintöjä lisätään vähitellen koodiin ja niitä parannetaan vähitellen. Mypyn ensimmäinen ajo oli edelleen hyvin hidas, koska siinä oli paljon riippuvuuksia tarkistettavana. Sitten tilanteen parantamiseksi otimme käyttöön etävälimuistimekanismin. Jos mypy havaitsee, että paikallinen välimuisti on todennäköisesti vanhentunut, se lataa koko koodikannan nykyisen välimuistin tilannevedoksen keskitetystä arkistosta. Sitten se suorittaa asteittaisen tarkistuksen käyttämällä tätä tilannekuvaa. Tämä on ottanut meidät jälleen yhden suuren askeleen kohti mypyn suorituskyvyn parantamista.

Tämä oli Dropboxin tyyppitarkistuksen nopean ja luonnollisen käyttöönoton aika. Vuoden 2016 loppuun mennessä meillä oli jo noin 420000 XNUMX riviä Python-koodia tyyppimerkinnöillä. Monet käyttäjät olivat innostuneita tyyppitarkastuksesta. Yhä useammat kehitystiimit käyttivät Dropbox mypyä.

Kaikki näytti silloin hyvältä, mutta meillä oli vielä paljon tehtävää. Aloimme tehdä säännöllisin väliajoin sisäisiä käyttäjätutkimuksia tunnistaaksemme projektin ongelma-alueita ja ymmärtääksemme, mitkä asiat pitää ensin ratkaista (tämä käytäntö on käytössä yhtiössä tänäkin päivänä). Tärkeimmät, kuten kävi selväksi, olivat kaksi tehtävää. Ensinnäkin tarvitsimme enemmän koodin kattavuutta, toiseksi tarvitsimme mypyn toimimaan nopeammin. Oli täysin selvää, että työmme mypyn nopeuttamiseksi ja sen toteuttamiseksi yritysprojekteihin oli vielä kaukana valmiista. Olemme täysin tietoisia näiden kahden tehtävän tärkeydestä ja ryhdyimme ratkaisemaan niitä.

Lisää tuottavuutta!

Lisätarkistukset tekivät mypystä nopeamman, mutta työkalu ei silti ollut tarpeeksi nopea. Monet lisätarkastukset kestivät noin minuutin. Syynä tähän oli syklinen tuonti. Tämä ei luultavasti yllätä ketään, joka on työskennellyt suurten Pythonilla kirjoitettujen koodikantojen kanssa. Meillä oli satojen moduulien sarjat, joista jokainen toi epäsuorasti kaikki muut. Jos jokin tuontisilmukan tiedosto muuttui, mypyn täytyi käsitellä kaikki kyseisen silmukan tiedostot ja usein kaikki moduulit, jotka toivat moduuleja kyseisestä silmukasta. Yksi tällainen sykli oli pahamaineinen "riippuvuuden vyyhti", joka aiheutti paljon ongelmia Dropboxissa. Kun tämä rakenne sisälsi useita satoja moduuleja, kun sitä tuotiin suoraan tai epäsuorasti monia testejä, sitä käytettiin myös tuotantokoodissa.

Harkitsimme mahdollisuutta "purkaa" pyöreät riippuvuudet, mutta meillä ei ollut resursseja tehdä sitä. Siellä oli liikaa koodia, jota emme tunteneet. Tämän seurauksena päädyimme vaihtoehtoiseen lähestymistapaan. Päätimme saada mypyn toimimaan nopeasti myös "riippuvuuskimppujen" ollessa olemassa. Saavutimme tämän tavoitteen käyttämällä mypy-demonia. Daemon on palvelinprosessi, joka toteuttaa kaksi mielenkiintoista ominaisuutta. Ensinnäkin se tallentaa tiedot koko koodikannasta muistiin. Tämä tarkoittaa, että joka kerta kun suoritat mypyn, sinun ei tarvitse ladata välimuistissa olevia tietoja, jotka liittyvät tuhansiin tuotuihin riippuvuuksiin. Toiseksi hän analysoi tarkasti pienten rakenneyksiköiden tasolla toimintojen ja muiden kokonaisuuksien välisiä riippuvuuksia. Esimerkiksi jos funktio foo kutsuu funktiota bar, sitten on riippuvuus foo alkaen bar. Kun tiedosto muuttuu, demoni ensin, erikseen, käsittelee vain muutetun tiedoston. Sitten se tarkastelee ulkoisesti näkyviä muutoksia kyseiseen tiedostoon, kuten muuttuneita funktioiden allekirjoituksia. Daemon käyttää yksityiskohtaisia ​​tuontitietoja vain tarkistaakseen ne funktiot, jotka todella käyttävät muokattua funktiota. Yleensä tällä lähestymistavalla sinun on tarkistettava hyvin vähän toimintoja.

Kaiken tämän toteuttaminen ei ollut helppoa, koska alkuperäinen mypy-toteutus keskittyi voimakkaasti yhden tiedoston käsittelyyn kerrallaan. Jouduimme käsittelemään monia rajatilanteita, joiden esiintyminen vaati toistuvia tarkastuksia tapauksissa, joissa jokin koodissa muuttui. Tämä tapahtuu esimerkiksi, kun luokalle on määritetty uusi perusluokka. Kun teimme mitä halusimme, pystyimme lyhentämään useimpien lisätarkistusten suoritusaikaa muutamaan sekuntiin. Tämä tuntui meille suurelta voitolta.

Vielä enemmän tuottavuutta!

Yhdessä edellä käsittelemäni etävälimuistin kanssa mypy-daemon ratkaisi lähes täysin ongelmat, jotka ilmenevät, kun ohjelmoija suorittaa usein tyyppitarkistusta tehden muutoksia pieneen määrään tiedostoja. Järjestelmän suorituskyky oli kuitenkin epäedullisimmassa käyttötapauksessa vielä kaukana optimaalisesta. Mypyn puhdas käynnistys voi kestää yli 15 minuuttia. Ja tämä oli paljon enemmän kuin olisimme olleet tyytyväisiä. Joka viikko tilanne paheni, kun ohjelmoijat jatkoivat uuden koodin kirjoittamista ja huomautusten lisäämistä olemassa olevaan koodiin. Käyttäjämme kaipasivat yhä enemmän suorituskykyä, mutta olimme iloisia voidessamme tavata heidät puolivälissä.

Päätimme palata yhteen aikaisemmista mypyä koskevista ajatuksista. Nimittäin Python-koodin muuttamiseksi C-koodiksi. Kokeilu Cythonilla (järjestelmä, jonka avulla voit kääntää Pythonilla kirjoitetun koodin C-koodiksi) ei tuonut meille mitään näkyvää nopeutta, joten päätimme elvyttää ajatuksen oman kääntäjän kirjoittamisesta. Koska mypy-koodikanta (kirjoitettu Pythonilla) sisälsi jo kaikki tarvittavat tyyppimerkinnät, ajattelimme, että kannattaa yrittää käyttää näitä merkintöjä järjestelmän nopeuttamiseksi. Tein nopeasti prototyypin testatakseni tätä ideaa. Se osoitti yli 10-kertaista suorituskykyä eri mikrovertailuissa. Ajatuksenamme oli kääntää Python-moduulit C-moduuleiksi Cythonilla ja muuttaa tyyppimerkinnät ajonaikaisiksi tyyppitarkistuksiksi (yleensä tyyppimerkinnät jätetään huomiotta ajon aikana ja niitä käyttävät vain tyypintarkistusjärjestelmät ). Aioimme itse asiassa kääntää mypy-toteutuksen Pythonista kielelle, joka oli suunniteltu staattisesti kirjoitettavaksi ja joka näyttäisi (ja suurimmaksi osaksi toimisi) täsmälleen kuten Python. (Tällaisesta kieltenvälisestä migraatiosta on tullut eräänlainen mypy-projektin perinne. Alkuperäinen mypy-toteutus kirjoitettiin Aloressa, sitten oli syntaktinen Java ja Python hybridi).

Keskittyminen CPython-laajennussovellusliittymään oli avainasemassa, jotta projektinhallintaominaisuudet eivät menettäisi. Meidän ei tarvinnut ottaa käyttöön virtuaalikoneita tai kirjastoja, joita mypy tarvitsi. Lisäksi meillä olisi edelleen pääsy koko Python-ekosysteemiin ja kaikkiin työkaluihin (kuten pytest). Tämä tarkoitti sitä, että pystyimme jatkamaan tulkitun Python-koodin käyttöä kehityksen aikana, jolloin pystyimme jatkamaan erittäin nopeaa koodimuutosten ja sen testaamisen mallia sen sijaan, että odottaisimme koodin kääntämistä. Näytti siltä, ​​että teimme hienoa työtä istuessamme niin sanotusti kahdella tuolilla, ja rakastimme sitä.

Kääntäjä, jota kutsuimme mypyc:ksi (koska se käyttää mypyä etupäänä tyyppien analysointiin), osoittautui erittäin onnistuneeksi projektiksi. Kaiken kaikkiaan saavutimme noin 4-kertaisen nopeuden toistuvilla mypy-ajoilla ilman välimuistia. Mypyc-projektin ytimen kehittäminen vei pieneltä Michael Sullivanin, Ivan Levkivskyn, Hugh Hahnin ja minulta koostuvan tiimin noin 4 kalenterikuukautta. Tämä työmäärä oli paljon pienempi kuin mitä olisi tarvittu mypyn uudelleenkirjoittamiseen esimerkiksi C++:ssa tai Gossa. Ja meidän piti tehdä paljon vähemmän muutoksia projektiin kuin olisimme joutuneet tekemään, kun kirjoitimme sen uudelleen toisella kielellä. Toivoimme myös saavamme mypycin sellaiselle tasolle, että muut Dropbox-ohjelmoijat voisivat käyttää sitä koodinsa kääntämiseen ja nopeuttamiseen.

Tämän suorituskyvyn saavuttamiseksi meidän piti soveltaa mielenkiintoisia teknisiä ratkaisuja. Näin ollen kääntäjä voi nopeuttaa monia operaatioita käyttämällä nopeita, matalan tason C-konstrukteja, esimerkiksi käännetty funktiokutsu muunnetaan C-funktiokutsuksi. Ja tällainen kutsu on paljon nopeampi kuin tulkitun funktion kutsuminen. Jotkut toiminnot, kuten sanakirjahaut, sisälsivät edelleen tavanomaisia ​​C-API-kutsuja CPythonista, jotka olivat vain hieman nopeampia käännettäessä. Pystyimme eliminoimaan tulkinnan aiheuttaman lisäkuormituksen järjestelmälle, mutta tämä toi tässä tapauksessa vain pienen suorituskyvyn lisäyksen.

Tunnistaaksemme yleisimmät "hitaat" toiminnot suoritimme koodiprofiloinnin. Näillä tiedoilla aseistettuna yritimme joko säätää mypyc:tä niin, että se tuottaisi nopeamman C-koodin tällaisille toiminnoille, tai kirjoittaa vastaavan Python-koodin uudelleen käyttämällä nopeampia toimintoja (ja joskus meillä ei yksinkertaisesti ollut tarpeeksi yksinkertaista ratkaisua siihen tai muuhun ongelmaan) . Python-koodin uudelleen kirjoittaminen oli usein helpompi ratkaisu ongelmaan kuin kääntäjän suorittaminen automaattisesti saman muunnoksen avulla. Pitkällä aikavälillä halusimme automatisoida monet näistä muutoksista, mutta tuolloin keskityimme nopeuttamaan mypyä vähällä vaivalla. Ja kohti tätä tavoitetta leikkasimme useita kulmia.

Jatkuu ...

Hyvä lukijat! Millaisia ​​vaikutelmia sait mypy-projektista, kun sait tietää sen olemassaolosta?

Polku 4 miljoonan Python-koodirivin tyyppitarkistukseen. Osa 2
Polku 4 miljoonan Python-koodirivin tyyppitarkistukseen. Osa 2

Lähde: will.com

Lisää kommentti