La vojo al tipokontrolado de 4 milionoj da linioj de Python-kodo. Parto 2

Hodiaŭ ni publikigas la duan parton de la traduko de la materialo pri kiel Dropbox organizis tipkontrolon por pluraj milionoj da linioj de Python-kodo.

La vojo al tipokontrolado de 4 milionoj da linioj de Python-kodo. Parto 2

Legu unu parton

Oficiala tipsubteno (PEP 484)

Ni faris niajn unuajn seriozajn eksperimentojn kun mypy ĉe Dropbox dum Hack Week 2014. Hack Week estas unu-semajna evento aranĝita de Dropbox. Dum ĉi tiu tempo, dungitoj povas labori pri tio, kion ili volas! Kelkaj el la plej famaj teknologiaj projektoj de Dropbox komenciĝis ĉe eventoj kiel ĉi tiuj. Kiel rezulto de ĉi tiu eksperimento, ni konkludis, ke mypy aspektas promesplena, kvankam la projekto ankoraŭ ne estas preta por vasta uzo.

Tiutempe, la ideo normigi Python-tipajn sugestajn sistemojn estis en la aero. Kiel mi diris, ekde Python 3.0 eblis uzi tipajn komentadojn por funkcioj, sed ĉi tiuj estis nur arbitraj esprimoj, sen difinitaj sintakso kaj semantiko. Dum programa ekzekuto, ĉi tiuj komentarioj estis, plejparte, simple ignoritaj. Post Hack Week, ni komencis labori pri normigado de semantiko. Ĉi tiu laboro kaŭzis la aperon PEP 484 (Guido van Rossum, Łukasz Langa kaj mi kunlaboris pri ĉi tiu dokumento).

Niaj motivoj povus esti rigardataj de du flankoj. Unue, ni esperis, ke la tuta Python-ekosistemo povus adopti oftan aliron al uzado de tipsugestoj (termino uzata en Python kiel la ekvivalento de "tipaj komentarioj"). Ĉi tio, konsiderante la eblajn riskojn, estus pli bona ol uzi multajn reciproke malkongruajn alirojn. Due, ni volis malkaŝe diskuti tipajn komentadajn mekanismojn kun multaj membroj de la Python-komunumo. Ĉi tiu deziro estis parte diktita de la fakto, ke ni ne volus aspekti kiel "apostatoj" el la bazaj ideoj de la lingvo en la okuloj de la larĝaj amasoj de Python-programistoj. Ĝi estas dinamike tajpita lingvo, konata kiel "anasa tajpado". En la komunumo, komence, iom suspektinda sinteno al la ideo de senmova tajpado ne povis ne aperi. Sed tiu sento finfine malkreskis post kiam evidentiĝis, ke senmova tajpado ne estos deviga (kaj post kiam homoj ekkomprenis, ke ĝi efektive utilas).

La tipa sugesta sintakso kiu estis poste adoptita estis tre simila al tio, kion mypy subtenis tiutempe. PEP 484 estis publikigita kun Python 3.5 en 2015. Python ne plu estis dinamike tajpita lingvo. Mi ŝatas pensi pri ĉi tiu evento kiel signifa mejloŝtono en Python-historio.

Komenco de migrado

Fine de 2015, Dropbox kreis teamon de tri homoj por labori pri mypy. Ili inkludis Guido van Rossum, Greg Price kaj David Fisher. Ekde tiu momento la situacio ege rapide disvolviĝis. La unua malhelpo al la kresko de mypy estis efikeco. Kiel mi sugestis supre, en la fruaj tagoj de la projekto mi pensis pri tradukado de la mypy efektivigo en C, sed ĉi tiu ideo estis forstrekita de la listo nuntempe. Ni estis blokitaj pri rulado de la sistemo uzante la CPython-interpretilon, kiu ne estas sufiĉe rapida por iloj kiel mypy. (La projekto PyPy, alternativa realigo de Python kun JIT-kompililo, ankaŭ ne helpis nin.)

Feliĉe, iuj algoritmaj plibonigoj venis al nia helpo ĉi tie. La unua potenca "akcelilo" estis la efektivigo de pliiga kontrolado. La ideo malantaŭ ĉi tiu plibonigo estis simpla: se ĉiuj dependecoj de la modulo ne ŝanĝiĝis ekde la antaŭa rulo de mypy, tiam ni povas uzi la datumojn konservitajn dum la antaŭa funkciado dum ni laboras kun dependecoj. Ni nur bezonis fari tajpkontroladon sur la modifitaj dosieroj kaj sur la dosieroj kiuj dependis de ili. Mypy eĉ iris iom plu: se la ekstera interfaco de modulo ne ŝanĝiĝis, mypy supozis, ke aliaj moduloj, kiuj importis ĉi tiun modulon, ne bezonas esti rekontrolitaj.

Pliiga kontrolado multe helpis nin kiam komentarios grandajn kvantojn de ekzistanta kodo. La punkto estas, ke ĉi tiu procezo kutime implikas multajn ripetantajn kurojn de mypy, ĉar komentarioj estas iom post iom aldonitaj al la kodo kaj iom post iom plibonigitaj. La unua kuro de mypy estis ankoraŭ tre malrapida ĉar ĝi havis multajn dependecojn por kontroli. Poste, por plibonigi la situacion, ni efektivigis foran kaŝmemoran mekanismon. Se mypy detektas, ke la loka kaŝmemoro verŝajne estos malaktuala, ĝi elŝutas la nunan kaŝmemorfoton por la tuta kodbazo el la centralizita deponejo. Ĝi tiam faras pliigan kontrolon uzante ĉi tiun momentfoton. Ĉi tio prenis nin unu pli grandan paŝon por pliigi la rendimenton de mypy.

Ĉi tio estis periodo de rapida kaj natura adopto de tipo-kontrolado ĉe Dropbox. Antaŭ la fino de 2016, ni jam havis proksimume 420000 liniojn de Python-kodo kun tipaj komentarioj. Multaj uzantoj estis entuziasmaj pri tipokontrolado. Pli kaj pli da evoluigteamoj uzis Dropbox mypy.

Ĉio aspektis bone tiam, sed ni ankoraŭ havis multon por fari. Ni komencis fari periodajn internajn uzantajn enketojn por identigi problemajn areojn de la projekto kaj kompreni, kiajn aferojn oni devas unue solvi (ĉi tiu praktiko ankoraŭ estas uzata en la kompanio hodiaŭ). La plej gravaj, kiel evidentiĝis, estis du taskoj. Unue, ni bezonis pli da tipa kovrado de la kodo, due, ni bezonis mypy por funkcii pli rapide. Estis tute klare, ke nia laboro por akceli mypy kaj efektivigi ĝin en kompaniajn projektojn estis ankoraŭ malproksime de kompleta. Ni, plene konsciaj pri la graveco de ĉi tiuj du taskoj, eksolvis ilin.

Pli da produktiveco!

Pliaj kontroloj faris mypy pli rapida, sed la ilo ankoraŭ ne estis sufiĉe rapida. Multaj pliigaj kontroloj daŭris ĉirkaŭ unu minuton. La kialo de tio estis ciklaj importoj. Ĉi tio verŝajne ne surprizos iun ajn, kiu laboris kun grandaj kodbazoj skribitaj en Python. Ni havis arojn da centoj da moduloj, ĉiu el kiuj nerekte importis ĉiujn aliajn. Se iu dosiero en importa buklo estis ŝanĝita, mypy devis prilabori ĉiujn dosierojn en tiu buklo, kaj ofte iujn ajn modulojn kiuj importis modulojn de tiu buklo. Unu tia ciklo estis la fifama "dependeca implikaĵo", kiu kaŭzis multajn problemojn ĉe Dropbox. Post kiam ĉi tiu strukturo enhavis plurajn centojn da moduloj, dum ĝi estis importita, rekte aŭ nerekte, multajn testojn, ĝi ankaŭ estis uzita en produktadokodo.

Ni pripensis la eblecon "malimpliki" cirkulajn dependecojn, sed ni ne havis la rimedojn por fari ĝin. Estis tro da kodo, kiun ni ne konis. Kiel rezulto, ni elpensis alternativan aliron. Ni decidis igi mypy funkcii rapide eĉ en ĉeesto de "dependaj implikaĵoj". Ni atingis ĉi tiun celon uzante la mypy-demonon. Demono estas servila procezo, kiu efektivigas du interesajn funkciojn. Unue, ĝi konservas informojn pri la tuta kodbazo en memoro. Ĉi tio signifas, ke ĉiufoje kiam vi rulas mypy, vi ne devas ŝargi konservitajn datumojn rilatajn al miloj da importitaj dependecoj. Due, li zorge, je la nivelo de malgrandaj strukturaj unuoj, analizas la dependecojn inter funkcioj kaj aliaj estaĵoj. Ekzemple, se la funkcio foo nomas funkcion bar, tiam estas dependeco foo el bar. Kiam dosiero ŝanĝiĝas, la demono unue, izolite, prilaboras nur la ŝanĝitan dosieron. Ĝi tiam rigardas ekstere videblajn ŝanĝojn al tiu dosiero, kiel ekzemple ŝanĝitaj funkciosubskriboj. La demono uzas detalajn informojn pri importado nur por kontroli tiujn funkciojn, kiuj efektive uzas la modifitan funkcion. Tipe, kun ĉi tiu aliro, vi devas kontroli tre malmultajn funkciojn.

Efektivigi ĉion ĉi ne estis facila, ĉar la origina mypy-efektivigo estis tre fokusita pri prilaborado de unu dosiero samtempe. Ni devis trakti multajn limajn situaciojn, kies okazo postulis ripetajn kontrolojn en kazoj kie io ŝanĝiĝis en la kodo. Ekzemple, tio okazas kiam klaso ricevas novan bazan klason. Post kiam ni faris tion, kion ni volis, ni povis redukti la ekzekuttempon de la plej multaj pliigaj kontroloj al nur kelkaj sekundoj. Ĉi tio ŝajnis al ni granda venko.

Eĉ pli da produktiveco!

Kune kun la fora kaŝmemoro, kiun mi diskutis supre, la mypy-demono preskaŭ tute solvis la problemojn, kiuj aperas kiam programisto ofte kuras tajpkontroladon, farante ŝanĝojn al malgranda nombro da dosieroj. Tamen, sistema rendimento en la malplej favora uzkazo estis ankoraŭ malproksima de optimuma. Pura ekfunkciigo de mypy povus daŭri pli ol 15 minutojn. Kaj ĉi tio estis multe pli ol ni estus feliĉaj. Ĉiusemajne la situacio plimalboniĝis, ĉar programistoj daŭre skribis novan kodon kaj aldonis komentadojn al ekzistanta kodo. Niaj uzantoj ankoraŭ malsatis je pli da rendimento, sed ni ĝojis renkonti ilin duonvoje.

Ni decidis reveni al unu el la pli fruaj ideoj pri mypy. Nome, por konverti Python-kodon en C-kodon. Eksperimentado kun Cython (sistemo, kiu ebligas al vi traduki kodon skribitan en Python en C-kodon) ne donis al ni videblan plirapidigon, do ni decidis revivigi la ideon verki nian propran kompililon. Ĉar la mypy-kodbazo (skribita en Python) jam enhavis ĉiujn necesajn tipajn komentadojn, ni pensis, ke indus provi uzi ĉi tiujn komentadojn por akceli la sistemon. Mi rapide kreis prototipon por testi ĉi tiun ideon. Ĝi montris pli ol 10-oblan pliiĝon en rendimento sur diversaj mikro-referenco. Nia ideo estis kompili Python-modulojn al C-moduloj uzante Cython, kaj igi tipajn komentadojn en rultempajn tipkontrolojn (kutime tajpaj komentarioj estas ignorataj ĉe rultempo kaj uzataj nur de tipkontrolaj sistemoj). Ni fakte planis traduki la mypy-efektivigon de Python en lingvon kiu estis desegnita por esti statike tajpita, kiu aspektus (kaj, plejparte, funkcius) ekzakte kiel Python. (Tia speco de translingva migrado fariĝis ia tradicio de la mypy-projekto. La origina mypy-efektivigo estis skribita en Alore, tiam estis sintaksa hibrido de Java kaj Python).

Fokigi la API de etendo de CPython estis ŝlosilo por ne perdi kapablojn pri projekt-administrado. Ni ne bezonis efektivigi virtualan maŝinon aŭ iujn ajn bibliotekojn, kiujn mypy bezonis. Krome, ni ankoraŭ havus aliron al la tuta Python-ekosistemo kaj ĉiuj iloj (kiel pytest). Ĉi tio signifis, ke ni povus daŭre uzi interpretitan Python-kodon dum evoluo, permesante al ni daŭrigi labori kun tre rapida ŝablono fari kodŝanĝojn kaj testi ĝin, anstataŭ atendi ke la kodo kompilos. Ŝajnis, ke ni faras bonegan laboron sidante sur du seĝoj, por tiel diri, kaj ni amis ĝin.

La kompililo, kiun ni nomis mypyc (ĉar ĝi uzas mypy kiel front-end por analizi tipojn), montriĝis tre sukcesa projekto. Ĝenerale, ni atingis proksimume 4x-rapidecon por oftaj mypy-kuroj sen kaŝmemoro. Evoluigi la kernon de la mypyc-projekto prenis malgrandan teamon de Michael Sullivan, Ivan Levkivsky, Hugh Hahn, kaj mi mem ĉirkaŭ 4 kalendaraj monatoj. Ĉi tiu kvanto de laboro estis multe pli malgranda ol kio estus bezonata por reverki mypy, ekzemple, en C++ aŭ Go. Kaj ni devis fari multe malpli da ŝanĝoj al la projekto ol ni devus fari dum reverkado en alia lingvo. Ni ankaŭ esperis, ke ni povus alporti mypyc al tia nivelo, ke aliaj Dropbox-programistoj povus uzi ĝin por kompili kaj akceli sian kodon.

Por atingi ĉi tiun nivelon de rendimento, ni devis apliki kelkajn interesajn inĝenierajn solvojn. Tiel, la kompililo povas akceli multajn operaciojn uzante rapidajn, malaltnivelajn konstrukciojn C. Ekzemple, kompilita funkciovoko estas tradukita en C-funkcivokon. Kaj tia voko estas multe pli rapida ol vokado de interpretita funkcio. Kelkaj operacioj, kiel ekzemple vortaraj serĉoj, daŭre implikis uzi regulajn C-API vokojn de CPython, kiuj estis nur marĝene pli rapidaj kiam kompilitaj. Ni povis forigi la aldonan ŝarĝon sur la sistemo kreita de interpretado, sed ĉi tio en ĉi tiu kazo donis nur malgrandan gajnon laŭ rendimento.

Por identigi la plej oftajn "malrapidajn" operaciojn, ni faris kodan profiladon. Armitaj kun ĉi tiuj datumoj, ni provis aŭ ĝustigi mypyc por ke ĝi generi pli rapidan C-kodon por tiaj operacioj, aŭ reverki la respondan Python-kodon uzante pli rapidajn operaciojn (kaj foje ni simple ne havis sufiĉe simplan solvon por tiu aŭ alia problemo) . Reskribi la Python-kodon ofte estis pli facila solvo al la problemo ol havi la kompililon aŭtomate plenumi la saman transformon. Longtempe, ni volis aŭtomatigi multajn el ĉi tiuj transformoj, sed tiutempe ni koncentriĝis pri rapidigo de mypy per minimuma peno. Kaj moviĝante al ĉi tiu celo, ni tranĉas plurajn angulojn.

Daŭrigota…

Karaj legantoj! Kiaj estis viaj impresoj pri la mypy-projekto kiam vi eksciis pri ĝia ekzisto?

La vojo al tipokontrolado de 4 milionoj da linioj de Python-kodo. Parto 2
La vojo al tipokontrolado de 4 milionoj da linioj de Python-kodo. Parto 2

fonto: www.habr.com

Aldoni komenton