Rruga për të verifikuar llojet në 4 milion rreshta kodi Python. Pjesa 2

Sot publikojmë pjesën e dytë të përkthimit të materialit mbi mënyrën se si Dropbox organizoi kontrollin e tipeve të disa milion linjave të kodit Python.

Rruga për të verifikuar llojet në 4 milion rreshta kodi Python. Pjesa 2

→ Lexoni pjesĂ«n e parĂ«

Mbështetje zyrtare për tipe (PEP 484)

Ne realizuam eksperimentet e para serioze me mypy në Dropbox gjatë Javës së Hack 2014. Java e Hack është një event që mbahet në Dropbox për një javë. Gjatë kësaj kohe, punonjësit mund të punojnë mbi çfarëdo që dëshirojnë! Disa nga projektet më të njohura teknologjike të Dropbox filluan pikërisht gjatë aktiviteteve të këtilla. Si rezultat i këtij eksperimenti, ne arritëm përfundime që mypy duket premtuese, megjithëse ky projekt akoma nuk ishte gati për përdorim të gjerë.

Në atë kohë, ideja për standardizimin e sistemeve të sugjerimeve të tipeve në Python ishte në ajër. Siç e thashë më parë, që nga Python 3.0 ishte e mundur të përdoren annotimet e tipeve për funksionet, por ato ishin shkallëzime të rastësishme, pa sintaksë dhe semantikë të caktuar. Gjatë ekzekutimit të programit, këto annotime, në shumicën e rasteve, thjesht injoroheshin. Pas Javës së Hack ne filluam të punojmë në standardizimin e semantikës. Ky punë çoi në krijimin e PEP 484 (në këtë dokument punuan së bashku Guido van Rossum, Luka Langa dhe unë).

Motivacionet tona mund tĂ« shqyrtoheshin nga dy perspektiva. SĂ« pari, ne shpresonim qĂ« e gjithĂ« ekosistemi i Python-it tĂ« mund tĂ« pranojĂ« njĂ« qasje tĂ« pĂ«rbashkĂ«t nĂ« pĂ«rdorimin e sugjerimeve pĂ«r tipet (type hints — njĂ« term i pĂ«rdorur nĂ« Python si njĂ« ekuivalent pĂ«r "annotimet e tipeve"). Kjo, duke marrĂ« parasysh rreziqet e mundshme, do tĂ« ishte mĂ« mirĂ« sesa pĂ«rdorimi i shumĂ« qasjeve tĂ« papajtueshme. SĂ« dyti, ne dĂ«shironim tĂ« diskutonim hapur mekanizmat e annotimit tĂ« tipeve me shumĂ« pĂ«rfaqĂ«sues tĂ« komunitetit tĂ« Python-it. PjesĂ«risht, kjo dĂ«shirĂ« u diktua nga fakti qĂ« nuk donim tĂ« dukeshim si "rrĂ«mbyes" tĂ« ideve thelbĂ«sore tĂ« gjuhĂ«s nĂ« sytĂ« e masave tĂ« gjera tĂ« programuesve tĂ« Python-it. Kjo Ă«shtĂ« njĂ« gjuhĂ« dinamike e tipizuar, e njohur pĂ«r "tipizimin pĂ«rmes patkove". NĂ« fillim, nĂ« komunitet, nuk mund tĂ« mos shfaqej njĂ« qĂ«ndrim pak dyshues ndaj idesĂ« sĂ« tipizimit statik. Por njĂ« qĂ«ndrim i tillĂ« pĂ«rfundimisht u zbut — pasi u bĂ« e qartĂ« se tipizimi statik nuk do tĂ« bĂ«hej obligativ (dhe pasi njerĂ«zit kuptuan se ishte me tĂ« vĂ«rtetĂ« i dobishĂ«m).

Sintaksa e pranuar për llojet ishte shumë e ngjashme me atë që mbështeste në atë kohë mypy. Dokumenti PEP 484 u publikua me Python 3.5 në vitin 2015. Python nuk ishte më një gjuhë që mbështeste vetëm tipizimin dinamik. Më pëlqen ta mendoj këtë ngjarje si një pikë të rëndësishme në historinë e Python.

Fillimi i migrimit

Në fund të vitit 2015, në Dropbox, u krijua një ekip prej tre personash për të punuar mbi mypy. Ata ishin Guido van Rossum, Greg Price dhe David Fisher. Që atëherë, situata filloi të zhvillohej shumë shpejt. Pengesa e parë në rrugën e rritjes së mypy ishte performanca. Siç e përmenda më lart, në periudhën e hershme të zhvillimit të projektit, mendoja të ktheja implementimin e mypy në gjuhën C, por kjo ide ishte përjashtuar për momentin. Ne mbetëm në atë që sistemi u ekzekutua nga interpretorin CPython, i cili nuk ishte mjaft i shpejtë për mjete si mypy. (Projekti PyPy, një implementim alternativ i Python me kompilim JIT, gjithashtu nuk na ndihmoi.)

FatkeqĂ«sisht, disa pĂ«rmirĂ«sime algoritmike na ndihmuan. NjĂ« "accelerator" i fuqishĂ«m ishte implementimi i verifikimit inkremental. Ideja e kĂ«tij pĂ«rmirĂ«simi ishte e thjeshtĂ«: nĂ«se tĂ« gjitha varĂ«sitĂ« e modulit qĂ« nga hera e kaluar qĂ« mypy u ekzekutua nuk kishin ndryshuar, atĂ«herĂ« mund tĂ« shfrytĂ«zonim, gjatĂ« punĂ«s me varĂ«sitĂ«, tĂ« dhĂ«nat e ruajtura gjatĂ« sesionit tĂ« mĂ«parshĂ«m tĂ« punĂ«s. Ne duhet tĂ« kryenim vetĂ«m verifikimin e tipeve nĂ« skedarĂ«t e ndryshuar dhe nĂ« ata skedarĂ« qĂ« varen prej tyre. Mypy shkoi edhe pĂ«rtej: nĂ«se ndĂ«rfaqja e jashtme e modulit nuk kishte ndryshuar — mypy e konsideronte se modulat e tjera qĂ« importojnĂ« kĂ«tĂ« modul nuk duhej tĂ« kontrolloheshin pĂ«rsĂ«ri.

Kontrolli incremental ishte shumĂ« i dobishĂ«m pĂ«r ne gjatĂ« anottimit tĂ« volumeve tĂ« mĂ«dha tĂ« kodit ekzistues. Çështja Ă«shtĂ« se ky proces zakonisht pĂ«rfshin shumĂ« ekzekutime iteruese tĂ« mypy, pasi anotimet shtohen gradualisht nĂ« kod dhe pĂ«rmirĂ«sohen gjithashtu nga ana e tyre. Ekzekutimi i parĂ« i mypy ende ishte shumĂ« i ngadalshĂ«m, pasi duhej tĂ« kontrolloheshin shumĂ« varĂ«si gjatĂ« tij. AtĂ«herĂ«, pĂ«r tĂ« pĂ«rmirĂ«suar situatĂ«n, implementuam njĂ« mekanizĂ«m tĂ« cache tĂ« largĂ«t. NĂ«se mypy e zbulojnĂ« se cache lokalisht Ă«shtĂ« ndoshta i vjetruar, ai ngarkon njĂ« snapshot aktual tĂ« cache pĂ«r tĂ« gjithĂ« bazĂ«n e kodit nga njĂ« depo tĂ« centralizuar. Pastaj, ai kryen, duke pĂ«rdorur kĂ«tĂ« snapshot, kontrollin incremental. Kjo na avancoi edhe njĂ« hap tĂ« konsiderueshĂ«m nĂ« rrugĂ«n pĂ«r tĂ« rritur performancĂ«n e mypy.

Ishte një periudhë e shpejtë dhe natyrale e implementimit të sistemit të kontrollit të tipeve në Dropbox. Në fund të vitit 2016, ne kishim rreth 420,000 linja kod Python me anotaime tipesh. Shumë përdorues e pranuan me entuziazëm kontrollin e tipeve. Në Dropbox, mypy ishte përdorur gjithnjë e më shumë nga skuadra të ndryshme të zhvilluesve.

NĂ« atĂ« kohĂ« gjithçka dukej mirĂ«, por na mbetej shumĂ« pĂ«r tĂ« bĂ«rĂ«. Filluam tĂ« kryenim anketa tĂ« brendshme periodike pĂ«r tĂ« identifikuar vendet problematike tĂ« projektit dhe pĂ«r tĂ« kuptuar se cilat çështje duheshin zgjidhur fillimisht (kjo praktikĂ« pĂ«rdoret edhe sot nĂ« kompani). Siç u kuptua, dy detyra ishin mĂ« tĂ« rĂ«ndĂ«sishmet. E para — duhej mĂ« shumĂ« mbulesĂ« tĂ« kodit me tipe, e dyta — mypy duhej tĂ« punonte mĂ« shpejt. Ishte krejtĂ«sisht e qartĂ« se puna jonĂ« pĂ«r tĂ« pĂ«rshpejtuar mypy dhe pĂ«r ta integruar atĂ« nĂ« projektet e kompanisĂ« ishte akoma shumĂ« larg pĂ«rfundimit. Duke e njohur plotĂ«sisht rĂ«ndĂ«sinĂ« e kĂ«tyre dy detyrave, morĂ«m pĂ«rsipĂ«r zgjidhjen e tyre.

Më shumë performancë!

Kontrollat e inkrementit kanë përshpejtuar mypy, por ky mjet ende nuk ishte mjaft i shpejtë. Shumë nga kontrollat e inkrementit zgjatnin rreth një minute. Arsyet e këtij të problemi ishin importet ciklike. Kjo, ka të ngjarë, nuk do ta befasonte atë që ka punuar me kode të mëdha të shkruara në Python. Ne kishim grupe me qindra module, secili prej të cilave importonte në mënyrë të tërthortë të gjitha të tjerat. Nëse ndonjë skedë në ciklin e importit ishte e ndryshuar, mypy duhej të përpunonte të gjitha skedarët që hynin në këtë cikël, dhe shpesh edhe çdo modul që importonte module nga ky cikël. Një nga këto cikle ishte i njohur si "gunga e varësive", e cila shkaktoi shumë probleme në Dropbox. Një herë, kjo strukturë përmbante disa qindra module, përveç kësaj, importohej, drejtpërsëdrejti ose tërthorazi, nga shumë teste, dhe përdorej gjithashtu në kodin e prodhimit.

Ne shqyrtuam mundësinë e "shkëputjes" së varësive ciklike, por nuk kishim burimet për ta bërë këtë. Kishim shumë kod të panjohur. Në fund, zgjodhëm një qasje alternative. Vendosëm që mypy të funksiononte shpejt, edhe me "përshtatjet e varësive". E arritëm këtë qëllim përmes demonit mypy. Demoni është një proces serveri, që ofron dy mundësi interesante. Së pari, ai mban në kujtesë informacionin për të gjithë bazën e kodit. Kjo do të thotë që çdo herë mypy nuk ka nevojë të ngarkojë të dhënat e ruajtura, që lidhen me mijëra varësi të importuara. Së dyti, ai analizoi me kujdes, në nivelin e strukturave të vogla, varësitë midis funksioneve dhe entiteteve të tjera. Për shembull, nëse funksioni foo thërret funksionin bar, atëherë ekziston një varësi foo nga bar. Kur ndërron një skedar, dëshira fillimisht përpunon vetëm skedarin që është ndryshuar, në izolim. Më pas, ajo shqyrton ndryshimet e këtij skedari që janë të dukshme nga jashtë, siç janë nënshkrimet e ndryshuara të funksioneve. Demon përdor informacionin e detajuar mbi importet vetëm për të verifikuar ato funksione që vërtet përdorin funksionin e ndryshuar. Zakonisht, me këtë qasje, duhet të kontrollohen vetëm disa funksione.

Implementimi i të gjitha këtyre ishte një detyrë e pazakontë, pasi implementimi origjinal i mypy ishte shumë i orientuar drejt përpunimit të një skedari në një kohë. Na duhej të përballeshim me një mori situatash të skajshme, ekzistenca e të cilave kërkonte rishikime kur ndodhte ndonjë ndryshim në kod. Për shembull, kjo ndodh kur një klase i caktohet një klasë bazë e re. Pasi bëmë atë që dëshironim, arritëm të zvogëlojmë kohën e ekzekutimit të shumicës së kontrolleve inkrementale në disa sekonda. Na dukej si një fitore e madhe.

Edhe më shumë performancë!

NĂ« pĂ«rputhje me cache-n nĂ« distancĂ« qĂ« e pĂ«rmenda mĂ« sipĂ«r, demon mypy zgjidhi praktikisht tĂ« gjitha problemet qĂ« ndodhnin kur programi kontrollonte shpesh llojet, duke bĂ«rĂ« ndryshime nĂ« njĂ« numĂ«r tĂ« vogĂ«l skedarĂ«sh. MegjithatĂ«, performanca e sistemit nĂ« variantin e tij mĂ« tĂ« pakĂ«ndshĂ«m pĂ«r pĂ«rdorim ende ishte larg optimales. NjĂ« start i pastĂ«r mypy mund tĂ« zgjaste mĂ« shumĂ« se 15 minuta. Kjo ishte shumĂ« mĂ« tepĂ«r se sa do tĂ« na pĂ«rshtatej. Çdo javĂ« situata bĂ«hej gjithnjĂ« e mĂ« e keqe, pasi programuesit vazhdonin tĂ« shkruanin kod tĂ« ri dhe tĂ« shtonin annotacione nĂ« kodin ekzistues. PĂ«rdoruesit tanĂ« ende dĂ«shironin mĂ« shumĂ« performancĂ«, ne ishim tĂ« gatshĂ«m tĂ« shkonim nĂ« pĂ«rputhje me ta.

Ne vendosĂ«m tĂ« kthehemi nĂ« njĂ« nga idetĂ« e hershme nĂ« lidhje me mypy. Konkretisht — nĂ« transformimin e kodit Python nĂ« kod C. Eksperimentet me Cython (Ă«shtĂ« njĂ« sistem qĂ« lejon tĂ« pĂ«rkthehet kodi i shkruar nĂ« Python nĂ« kod C) nuk na dhanĂ« ndonjĂ« pĂ«rmirĂ«sim tĂ« dukshĂ«m, ndaj vendosĂ«m tĂ« ringjallim idenĂ« e krijimit tĂ« njĂ« kompileri tĂ« vetin. Duke qenĂ« se baza e kodit mypy (e shkruar nĂ« Python) pĂ«rfshinte tashmĂ« tĂ« gjitha annotimet e nevojshme tĂ« tipeve, na dukej e vlerĂ«sueshme tĂ« provonim t'i pĂ«rdorim ato annotime pĂ«r tĂ« pĂ«rshpejtuar funksionimin e sistemit. UnĂ« krijova shpejt njĂ« prototip pĂ«r tĂ« testuar kĂ«tĂ« ide. Ai tregoi nĂ« mikro-benchmarks tĂ« ndryshme rritje tĂ« performancĂ«s mĂ« shumĂ« se 10 herĂ«. Ideja jonĂ« ishte tĂ« kompilonim modulĂ«t Python nĂ« modulĂ« C duke pĂ«rdorur Cython, dhe qĂ« tĂ« shndĂ«rronim annotimet e tipeve nĂ« kontrolle tipesh qĂ« realizohen gjatĂ« ekzekutimit tĂ« programit (zakonisht, annotimet e tipeve injorohen gjatĂ« ekzekutimit tĂ« programeve dhe pĂ«rdoren vetĂ«m nga sistemet e kontrollit tĂ« tipeve). Ne, nĂ« fakt, planifikonim tĂ« transferonim implementimin e mypy nga Python nĂ« njĂ« gjuhĂ« qĂ« Ă«shtĂ« krijuar tĂ« jetĂ« e tipizuar statikisht, e cila do tĂ« dukej (dhe, pĂ«r shumicĂ«n e saj, do tĂ« funksiononte) ashtu si Python. (Kjo lloj migrimi ndĂ«rgjuhĂ«sor ka bĂ«rĂ« njĂ« lloj tradite tĂ« projektit mypy. Implementimi fillestar i mypy ishte shkruar nĂ« Alore, dhe mĂ« pas ishte njĂ« hibrid sintaksor i Java dhe Python).

Fokusimi në API-t e zgjerimeve CPython ishte çelësi për të mos humbur mundësitë e menaxhimit të projektit. Nuk kishte nevojë të implementonim një makinë virtuale ose çdo bibliotekë që mypy kishte nevojë. Për më tepër, ne ende do të kishim qasje në të gjithë ekosistemin e Python-it, do të ishin në dispozicion të gjithë mjetet (siç është pytest). Kjo do të thoshte se ne do të mund të vazhdonim të përdornim kodin interpretues të Python-it gjatë zhvillimit, duke na lejuar që të vazhdonim të punonim me një skemë shumë të shpejtë për ndryshimin e kodit dhe testimin e tij, në vend që të prisnim për kompilimin e kodit. Ishte sikur na shkonte mrekullisht, siç thuhet, të rrinim në dy karrige, dhe na pëlqente kjo.

Kompilatori që e quajtëm mypyc (sepse ai përdor mypy për analizimin e tipeve) doli të jetë një projekt mjaft i suksesshëm. Në përgjithësi, arritëm një rritje prej rreth 4 herë në kohën e nisjes së shpeshtë të mypy pa përdorur caching. Zhvillimi i bërthamës së projektit mypyc mori rreth 4 muaj kalendarikë për një ekip të vogël që përfshinte Michael Sullivan, Ivan Levkivsky, Hugh Han dhe mua. Ky volum pune ishte shumë më i vogël se sa do të nevojitej për të shkruar përsëri mypy, për shembull në C++ ose Go. Ndryshimet që na duhej të bënim në projekt gjithashtu ishin shumë më të pakta se ato që do të ishin të nevojshme për ta shkruar atë në një gjuhë tjetër. Ne gjithashtu shpresonim se do të arrinim ta çonim mypyc në një nivel ku programuesit e tjerë nga Dropbox do të mund ta përdorin për të kompiluar dhe përshpejtuar kodin e tyre.

Për të arritur një nivel të tillë performance, na duhej të zbatonim disa zgjidhje inxhinierike interesante. Kështu, kompajleri mund të përshpejtojë ekzekutimin e shumë operacioneve falë përdorimit të strukturave të shpejta të niveleve të ulëta në C. Për shembull, thirrja e një funksioni të kompajluar përkthehet në një thirrje të funksionit C. Një thirrje e tillë ekzekutohet shumë më shpejt se një thirrje e funksionit të interpretuar. Disa operacione, të tilla si kërkimi në fjalor, ende përfshinin përdorimin e thirrjeve të zakonshme të C-API nga CPython, të cilat, pas kompajlimit, rezultuan vetëm pak më të shpejta. Arritëm të eliminojmë ngarkesën shtesë në sistem, e shkaktuar nga interpretimi, por në këtë rast, ky i fundit ofroi vetëm një fitim të vogël në aspektin e performancës.

Për të identifikuar operacionet më të zakonshme "të ngadalta", kryem profilizimin e kodit. Të pajisur me të dhënat e marra, përpiqeshim ose ta "rregullojmë" mypyc-in që të gjeneronte më shumë kod C të shpejtë për operacione të tilla, ose të rishkruanim kodin përkatës Python duke përdorur operacione më të shpejta (ndonjëherë nuk kishim një zgjidhje të thjeshtë për ndonjë problem). Rishkrimi i kodit Python shpesh rezultonte të ishte një zgjidhje më e lehtë për problemin se sa implementimi i ekzekutimit automatik të të njëjtës transformim në kompilator. Në afatgjatë, dëshironim të automatizonim shumë nga këto transformime, por në atë moment ishim të fokusuar në përshpejtimin e mypy me sa më pak përpjekje. Dhe, duke ecur drejt kësaj qëllimi, preknim disa kënde.

Vazhdon


Të nderuar lexues! Cilat ndjenja ju ka shkaktuar projekti mypy kur mësuat për ekzistencën e tij?

Rruga për të verifikuar llojet në 4 milion rreshta kodi Python. Pjesa 2
Rruga për të verifikuar llojet në 4 milion rreshta kodi Python. Pjesa 2

Burimi: habr.com

Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« đŸ”„ Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« | ProHoster