Die pad na tikkontrolering van 4 miljoen reëls Python-kode. Deel 2

Vandag publiseer ons die tweede deel van die vertaling van die materiaal oor hoe Dropbox tipe beheer van etlike miljoen reëls Python-kode georganiseer het.

Die pad na tikkontrolering van 4 miljoen reëls Python-kode. Deel 2

Lees die eerste deel

Amptelike tipe ondersteuning (PEP 484)

Ons het ons eerste ernstige eksperimente met mypy op Dropbox gedoen tydens Hack Week 2014. Hack Week is 'n een-week-geleentheid wat deur Dropbox aangebied word. Gedurende hierdie tyd kan werknemers aan enigiets werk! Sommige van Dropbox se bekendste tegnologieprojekte het by geleenthede soos hierdie begin. As gevolg van hierdie eksperiment het ons tot die gevolgtrekking gekom dat mypy belowend lyk, hoewel hierdie projek nog nie gereed was vir wydverspreide gebruik nie.

Destyds was die idee om Python se tipe wenkstelsels te standaardiseer in die lug. Soos ek gesê het, sedert Python 3.0 was dit moontlik om tipe-aantekeninge op funksies te gebruik, maar dit was net arbitrêre uitdrukkings, met geen gedefinieerde sintaksis of semantiek nie. Tydens programuitvoering is hierdie aantekeninge vir die grootste deel eenvoudig geïgnoreer. Na Hack Week het ons begin werk aan semantiese standaardisering. Hierdie werk het gelei tot die ontstaan PEP 484 (Guido van Rossum, Lukasz Langa en ek het saam aan hierdie dokument gewerk).

Ons motiewe kon van twee kante bekyk word. Eerstens het ons gehoop dat die hele Python-ekosisteem 'n algemene benadering tot die gebruik van tipe-wenke kan aanneem (tipe-wenke is 'n term wat in Python gebruik word as 'n analoog van "tipe-aantekeninge"). Dit, gegewe die moontlike risiko's, sou beter wees as om baie wedersyds onversoenbare benaderings te gebruik. Tweedens wou ons die meganika van tipe annotasie openlik met baie mense in die Python-gemeenskap bespreek. Deels is hierdie begeerte gedikteer deur die feit dat ons nie soos "afvalliges" van die basiese idees van die taal in die oë van die algemene publiek van Python-programmeerders sou wou lyk nie. Dit is 'n dinamies getikte taal wat bekend is vir "eendtik". In die gemeenskap, aan die begin, kon 'n ietwat verdagte houding teenoor die idee van statiese tik nie anders as om te ontstaan ​​nie. Maar daardie sentiment het uiteindelik vervaag nadat dit duidelik geword het dat statiese tik nie verpligtend gaan wees nie (en nadat mense besef het dit is regtig nuttig).

Die tipe wenk-sintaksis wat uiteindelik aangeneem is, was baie soortgelyk aan dié wat destyds deur mypy ondersteun is. PEP 484 is in 3.5 saam met Python 2015 vrygestel. Python was nie meer 'n dinamies getikte taal nie. Ek hou daarvan om aan hierdie gebeurtenis te dink as 'n mylpaal in die geskiedenis van Python.

Begin van migrasie

Aan die einde van 2015 is 'n span van drie mense by Dropbox geskep om aan mypy te werk. Dit het Guido van Rossum, Greg Price en David Fisher ingesluit. Van daardie oomblik af het die situasie baie vinnig begin ontwikkel. Die eerste struikelblok vir die groei van mypy was prestasie. Soos ek hierbo laat deurskemer het, het ek in die vroeë dae van die projek daaraan gedink om die mypy-implementering in C te vertaal, maar daardie idee is vir eers van die lys geskrap. Ons is vasgevang op die feit dat die CPython-tolk gebruik is om die stelsel te begin, wat nie vinnig genoeg is vir gereedskap soos mypy nie. (Die PyPy-projek, 'n alternatiewe implementering van Python met 'n JIT-samesteller, het ook nie gehelp nie.)

Gelukkig het 'n paar algoritmiese verbeterings hier tot ons hulp gekom. Die eerste kragtige "versneller" was die implementering van inkrementele tjeks. Die idee agter hierdie verbetering was eenvoudig: as al die module se afhanklikhede nie verander het sedert die vorige loop van mypy nie, dan kan ons die data wat tydens die vorige sessie gekas is, gebruik om met afhanklikhede te werk. Ons het net nodig gehad om tipe kontrole uit te voer op die veranderde lêers en op daardie lêers wat daarvan afhang. Mypy het selfs 'n bietjie verder gegaan: as die eksterne koppelvlak van 'n module nie verander het nie, was mypy van mening dat ander modules wat hierdie module invoer, nie weer nagegaan hoef te word nie.

Inkrementele kontrolering het ons baie gehelp toe ons groot hoeveelhede bestaande kode annoteer. Die punt is dat hierdie proses gewoonlik baie iteratiewe lopies van mypy behels, aangesien aantekeninge geleidelik by die kode gevoeg en geleidelik verbeter word. Die eerste loop van mypy was nog steeds baie stadig, aangesien dit vereis het dat baie afhanklikhede nagegaan moes word. Toe, om die situasie te verbeter, het ons 'n afstandkasmeganisme geïmplementeer. As mypy bespeur dat die plaaslike kas waarskynlik verouderd is, laai dit die huidige kas-snapshot vir die hele kodebasis van die gesentraliseerde bewaarplek af. Dit voer dan 'n inkrementele kontrole uit met behulp van daardie momentopname. Dit is een groot stap vorentoe in mypy se prestasieverbeteringsreis.

Dit was 'n tydperk van vinnige en natuurlike aanvaarding van Dropbox se tipe kontroleringstelsel. Teen die einde van 2016 het ons reeds sowat 420000 XNUMX reëls Python-kode met tipe-aantekeninge gehad. Baie gebruikers het entoesiasties geraak oor tipe nagaan. Meer ontwikkelingspanne het Dropbox mypy gebruik.

Alles het toe goed gelyk, maar ons het nog baie gehad om te doen. Ons het begin om periodieke interne gebruikersopnames uit te voer om probleemareas van die projek te identifiseer en te verstaan ​​watter kwessies eers opgelos moet word (hierdie praktyk word vandag nog in die maatskappy gebruik). Die belangrikste, soos dit duidelik geword het, was twee take. Die eerste was dat ons meer kodedekking met tipes nodig gehad het, die tweede was dat mypy vinniger moes werk. Dit was baie duidelik dat ons werk om mypy te bespoedig en dit in die maatskappy se projekte te kry nog lank nie verby was nie. Ons, wat die belangrikheid van hierdie twee take ten volle besef het, het hul oplossing aanvaar.

Meer prestasie!

Inkrementele tjeks het mypy vinniger gemaak, maar dit was steeds nie vinnig genoeg nie. Baie inkrementele tjeks het ongeveer 'n minuut geduur. Die rede hiervoor was sikliese invoere. Dit sal waarskynlik nie iemand verbaas wat met groot kodebasisse gewerk het wat in Python geskryf is nie. Ons het stelle van honderde modules gehad, wat elkeen indirek al die ander ingevoer het. As enige lêer in die invoerlus verander het, moes mypy al die lêers in daardie lus verwerk, en dikwels ook enige modules wat modules van daardie lus invoer. Een so 'n siklus was die berugte "verslawing-tangle" wat baie probleme by Dropbox veroorsaak het. Sodra hierdie struktuur etlike honderde modules bevat het, terwyl dit direk of indirek baie toetse ingevoer is, is dit ook in die produksiekode gebruik.

Ons het dit oorweeg om sirkelafhanklikhede te ontrafel, maar ons het nie die hulpbronne gehad om dit te doen nie. Daar was te veel kode waarmee ons nie vertroud was nie. Gevolglik het ons met 'n alternatiewe benadering vorendag gekom. Ons het besluit om mypy vinnig te laat hardloop, selfs met "afhanklikheidstangles". Ons het hierdie doel bereik met die mypy daemon. 'n Daemon is 'n bedienerproses wat twee interessante kenmerke implementeer. Eerstens hou dit inligting oor die hele kodebasis in die geheue. Dit beteken dat elke keer as jy mypy laat loop, jy nie data wat met duisende ingevoerde afhanklikhede verband hou, hoef te laai nie. Tweedens ontleed hy noukeurig, op die vlak van klein strukturele eenhede, die afhanklikhede tussen funksies en ander entiteite. Byvoorbeeld, as die funksie foo roep 'n funksie bar, dan is daar 'n afhanklikheid foo van bar. Wanneer 'n lêer verander, verwerk die daemoon eers, in isolasie, slegs die veranderde lêer. Dit soek dan na ekstern sigbare veranderinge aan daardie lêer, soos veranderde funksie-handtekeninge. Die daemon gebruik gedetailleerde inligting oor invoere slegs om die funksies wat die gewysigde funksie gebruik, te verdubbel. Gewoonlik, met hierdie benadering, is daar baie min funksies om na te gaan.

Dit was moeilik om dit alles te implementeer, aangesien die oorspronklike implementering van mypy sterk gefokus was op die verwerking van een lêer op 'n slag. Ons moes baie randsituasies hanteer, waarvan die voorkoms herhaalde kontroles vereis het in gevalle waar iets in die kode verander het. Byvoorbeeld, dit gebeur wanneer 'n klas 'n nuwe basisklas toegeken word. Nadat ons gedoen het wat ons wou, kon ons die uitvoeringstyd van die meeste inkrementele tjeks tot 'n paar sekondes verminder. Dit het vir ons na 'n groot oorwinning gelyk.

Selfs meer prestasie!

Saam met die afgeleë caching waaroor ek hierbo gepraat het, het die mypy-demoon die probleme wat ontstaan ​​wanneer 'n programmeerder dikwels tipe kontrolering uitvoer, amper heeltemal opgelos, en veranderinge aan 'n klein aantal lêers aanbring. Stelselwerkverrigting in sy ergste gebruiksgeval was egter nog ver van optimaal. 'n Skoon begin van mypy kan meer as 15 minute neem. En dit was baie meer as wat ons sou wou hê. Die situasie het elke week erger geword namate programmeerders aanhou om nuwe kode te skryf en aantekeninge by bestaande kode te voeg. Ons gebruikers was steeds lus vir meer prestasie, maar ons was bly om hulle te ontmoet.

Ons het besluit om terug te gaan na een van die vroeë mypy-idees. Naamlik om Python-kode na C-kode om te skakel. Eksperimente met Cython (dit is 'n stelsel wat jou toelaat om kode wat in Python geskryf is in C-kode te vertaal) het ons geen sigbare versnelling gegee nie, so ons het besluit om die idee om ons eie samesteller te skryf, te laat herleef. Aangesien die mypy-kodebasis (geskryf in Python) reeds al die nodige tipe-aantekeninge bevat het, het dit die moeite werd gelyk om hierdie aantekeninge te probeer gebruik om die stelsel te bespoedig. Ek het vinnig 'n prototipe geskep om hierdie idee te toets. Dit het meer as 'n 10-voudige toename in prestasie op verskeie mikro-maatstawwe getoon. Ons idee was om Python-modules saam te stel in C-modules met behulp van Cython, en tipe-aantekeninge in runtime-tipekontroles te verander (gewoonlik word tipe-aantekeninge tydens looptyd geïgnoreer en slegs deur tipetoetsers gebruik). Ons was in werklikheid van plan om mypy se implementering vanaf Python te vertaal in 'n taal wat staties getik geskep is, wat presies soos Python sou lyk (en, vir die grootste deel, werk). (Hierdie soort kruistaalmigrasie het iets van 'n mypy-projektradisie geword. Die oorspronklike implementering van mypy is in Alore geskryf, toe was daar 'n sintaktiese baster van Java en Python).

Om op die CPython-uitbreidings-API te fokus, was die sleutel om nie projekbestuurvermoëns te verloor nie. Ons hoef nie 'n virtuele masjien of enige biblioteke te implementeer wat mypy nodig gehad het nie. Daarbenewens sal die hele Python-ekosisteem steeds vir ons beskikbaar wees, alle gereedskap (soos pytest) sal beskikbaar wees. Dit het beteken dat ons kon voortgaan om geïnterpreteerde Python-kode tydens ontwikkeling te gebruik, wat ons in staat sou stel om voort te gaan met 'n baie vinnige skema om veranderinge aan die kode aan te bring en dit te toets, eerder as om te wag vir die kode om saam te stel. Dit het gelyk of ons so te sê goed vaar terwyl ons op twee stoele gesit het, en ons het daarvan gehou.

Die samesteller, wat ons mypyc genoem het (omdat dit mypy as 'n front-end vir ontleding van tipes gebruik), blyk 'n baie suksesvolle projek te wees. Algehele - ons het ongeveer 4x versnelling van gereelde mypy-bekendstellings behaal sonder om kas te gebruik. Dit het 'n klein span van Michael Sullivan, Ivan Levkivsky, Hugh Khan en myself geneem om die kern van die mypyc-projek in ongeveer 4 kalendermaande te ontwikkel. Hierdie hoeveelheid werk was baie minder ambisieus as wat nodig sou wees om mypy te herskryf, byvoorbeeld in C ++ of in Go. En ons moes baie minder veranderinge aan die projek aanbring as wat ons sou moes aanbring wanneer ons dit in 'n ander taal herskryf. Ons het ook gehoop dat ons mypyc tot so 'n vlak sou kon bring dat ander programmeerders van Dropbox dit kon gebruik om hul kode saam te stel en te bespoedig.

Om hierdie vlak van prestasie te bereik, moes ons 'n paar interessante ingenieursoplossings toepas. Die samesteller kan byvoorbeeld baie bewerkings bespoedig deur vinnige, laevlak C-konstrukte te gebruik. Byvoorbeeld, 'n oproep na 'n saamgestelde funksie word vertaal in 'n oproep na 'n C-funksie. En so 'n oproep is baie vinniger as om 'n geïnterpreteerde funksie te roep. Sommige bewerkings, soos woordeboekopsoeke, was steeds beperk tot die gebruik van normale CPython C-API-oproepe, wat net effens vinniger was na samestelling. Ons kon ontslae raak van die bykomende las op die stelsel wat deur die interpretasie geskep is, maar dit het in hierdie geval slegs 'n klein prestasiewins gegee.

Om die mees algemene "stadige" bewerkings te identifiseer, het ons kodeprofilering uitgevoer. Gewapen met hierdie data, het ons probeer om mypyc óf aan te pas om vinniger C-kode vir hierdie bewerkings te genereer, óf die ooreenstemmende Python-kode te herskryf om vinniger bewerkings te gebruik (en soms het ons net nie 'n eenvoudige genoeg oplossing daarvoor gehad nie) óf ander probleem ). Die herskryf van die Python-kode het dikwels bewys dat dit 'n makliker oplossing vir 'n probleem is as om die samesteller dieselfde transformasie outomaties te laat doen. Op die lange duur wou ons baie van hierdie transformasies outomatiseer, maar ons was destyds daarop gefokus om mypy met so min moontlik moeite te bespoedig. En ons, wat na hierdie doel beweeg, het verskeie hoeke afgesny.

Vervolg…

Beste lesers! Wat was jou indrukke van die mypy-projek toe jy daarvan uitgevind het?

Die pad na tikkontrolering van 4 miljoen reëls Python-kode. Deel 2
Die pad na tikkontrolering van 4 miljoen reëls Python-kode. Deel 2

Bron: will.com

Voeg 'n opmerking