CeļŔ uz 4 miljonu rindu Python koda tipu pārbaudi. Otrā daļa

Šodien publicējam otro daļu par materiālu par to, kā Dropbox organizēja tipu kontroli daudziem miljoniem Python kodu rindiņu.

CeļŔ uz 4 miljonu rindu Python koda tipu pārbaudi. Otrā daļa

→ LasÄ«t pirmo daļu

Oficiālā tipu atbalsts (PEP 484)

Mēs veicām pirmos nopietnos eksperimentus ar mypy Dropbox laikā Hack Week 2014. Hack Week ir pasākums, ko Dropbox organizē vienas nedēļas garumā. Å ajā laikā darbinieki var strādāt pie jebkā! Daži no slavenākajiem tehnoloÄ£iju projektiem Dropbox sākās tieÅ”i Å”ajos pasākumos. Å Ä« eksperimenta rezultātā mēs nonācām pie secinājuma, ka mypy izskatās solÄ«gi, lai gan Å”is projekts vēl nebija gatavs plaÅ”ai izmantoÅ”anai.

Tajā laikā gaisā virmoja ideja par Python tipu norāžu sistēmu standartizēŔanu. Kā jau teicu, sākot no Python 3.0 bija iespējams izmantot tipu anotācijas funkcijām, taču tās bija tikai nejauÅ”i izteikumi, bez definēta sintakses un semantikas. Izpildes laikā Ŕīs anotācijas lielākoties tika vienkārÅ”i ignorētas. Pēc Hack Week mēs sākām strādāt pie semantikas standartizēŔanas. Å is darbs noveda pie PEP 484 (uz Å”o dokumentu kopÄ«gi strādāja Gvido van Rossum, Luka Lange un es).

MÅ«su motivācija varēja tikt skatÄ«ta no divām pusēm. Pirmkārt, mēs cerējām, ka visa Python ekosistēma varētu pieņemt kopÄ«gu pieeju tipu norāžu (type hints - termins, kas tiek izmantots Python kā analoÄ£ija ā€žtipu anotācijāmā€) izmantoÅ”anai. Tas, ņemot vērā iespējamos riskus, bÅ«tu labāk nekā izmantot daudzus savstarpēji nesavienojamus pieejas. Otrkārt, mēs vēlējāmies atklāti apspriest tipu anotācijas mehānismus ar daudziem Python kopienes pārstāvjiem. Daļēji Å”o vēlmi noteica tas, ka mēs nevēlējāmies izskatÄ«ties kā ā€žnospiedējiā€ pret pamata valodas idejām plaŔā Python programmētāju acÄ«s. Tas ir dinamiski tipizēts valoda, kas pazÄ«stama ar ā€žpÄ«les tipizācijuā€. Kopienā, sākumā nevarēja neizcelties nedaudz aizdomÄ«ga attieksme pret statiskās tipizācijas ideju. Bet Ŕāds noskaņojums galu galā vājāks - pēc tam, kad kļuva skaidrs, ka statiskā tipizācija netiek plānota darÄ«t obligātu (un pēc tam, kad cilvēki saprata, ka tā patieŔām ir noderÄ«ga).

Pieņemtais sintakse veids bija ļoti lÄ«dzÄ«gs tam, ko tajā laikā atbalstÄ«ja mypy. PEP 484 dokuments tika izlaists kopā ar Python 3.5 2015. gadā. Python vairs nebija valoda, kas atbalsta tikai dinamisko tipizāciju. Man patÄ«k domāt par Å”o notikumu kā par nozÄ«mÄ«gu pagrieziena punktu Python vēsturē.

Migrācijas sākums

2015. gada beigās Dropbox tika izveidota trÄ«s cilvēku komanda darbam pie mypy. Tajā bija Gvido van Rossum, Gregs Praiss un Deivids FiÅ”ers. No Ŕī brīža situācija sāka strauji attÄ«stÄ«ties. Pirmais Ŕķērslis mypy izaugsmei bija veiktspēja. Kā jau iepriekÅ” minēju, agrÄ«najā projekta attÄ«stÄ«bas posmā es domāju par mypy realizācijas pārvietoÅ”anu uz C valodu, bet Ŕī ideja tika atcelta. Mēs iestrēgām ar to, ka sistēmas palaiÅ”anai tika izmantots CPython interpreters, kas nav pietiekami ātrs tādiem rÄ«kiem kā mypy. (Projekts PyPy, alternatÄ«va Python realizācija ar JIT kompilatoru, arÄ« mums nepalÄ«dzēja.)

Par laimi, Å”eit mums palÄ«gā nāca daži algoritmiskie uzlabojumi. Pirmais jaudÄ«gais "paātrinātājs" bija inkrementālās pārbaudes realizācija. Å Ä« uzlabojuma ideja bija vienkārÅ”a: ja kopÅ” iepriekŔējās mypy palaiÅ”anas visas moduļa atkarÄ«bas nav mainÄ«juŔās, tad mēs varam izmantot, strādājot ar atkarÄ«bām, iepriekŔējā sesijā kausētus datus. Mums bija nepiecieÅ”ams tikai veikt tipu pārbaudi mainÄ«tajos failos un failos, kas no tiem bija atkarÄ«gi. Mypy gāja pat vēl tālāk: ja moduļa ārējā saskarne nebija mainÄ«jusies, mypy uzskatÄ«ja, ka citi moduļi, kas importē Å”o moduli, nav jāverificē atkārtoti.

Inkrēmentālā pārbaude ļoti palÄ«dzēja mums, anotējot lielus esoŔā koda apjomus. Proti, Å”is process parasti ietver daudzas iteratÄ«vas mypy palaiÅ”anas, jo anotācijas pakāpeniski tiek pievienotas kodam un pakāpeniski uzlabojas. Pirmais mypy palaiÅ”ana joprojām bija ļoti lēna, jo tās izpildes laikā bija jāpārbauda daudz atkarÄ«bu. Tāpēc mēs, lai uzlabotu situāciju, Ä«stenojām attālinātās keÅ”atmiņas mehānismu. Ja mypy atklāj, ka lokālā keÅ”atmiņa, iespējams, ir novecojusi, tas lejupielādē aktuālo keÅ”atmiņas attēlu visai kodu bāzei no centralizētā repozitorija. Pēc tam tas veic, izmantojot Å”o attēlu, inkrēmentālo pārbaudi. Tas bija vēl viens bÅ«tisks solis mÅ«su ceļā uz mypy veiktspējas palielināŔanu.

Tā bija ātras un dabiski ieviestas tipa pārbaudes sistēmas ievieÅ”anas perioda Dropbox. Mums lÄ«dz 2016. gada beigām jau bija aptuveni 420000 rindu Python koda ar tipu anotācijām. Daudzi lietotāji ar entuziasmu uzņēma tipu pārbaudi. Dropbox mypy arvien vairāk izmantoja dažādas izstrādātāju komandas.

Toreiz viss izskatÄ«jās labi, taču mums vēl bija daudz darāmā. Mēs sākām veikt periodiskus iekŔējus lietotāju aptaujas, lai identificētu projekta vājās vietas un saprastu, kādas problēmas jārisina vispirms (Ŕī prakse tiek izmantota uzņēmumā arÄ« mÅ«sdienās). Visdziļākais, kā izrādÄ«jās, bija divas uzdevumu grupas. Pirmais — nepiecieÅ”ams lielāks koda segums ar tipiem, otrais — nepiecieÅ”ams, lai mypy darbotos ātrāk. PilnÄ«gi skaidri bija tas, ka mÅ«su darbs pie mypy paātrināŔanas un tā ievieÅ”anas uzņēmuma projektos vēl nebija pabeigts. Apzinoties Å”o divu uzdevumu svarÄ«gumu, mēs ķērāmies pie to risināŔanas.

Vairāk veiktspējas!

Inkrimentālās pārbaudes paātrināja mypy, taču Å”is rÄ«ks joprojām nebija pietiekami ātrs. Daudzas inkrimentālās pārbaudes aizņēma aptuveni minÅ«ti. Iemesls tam bija cikliskie importi. Tas, iespējams, nepārsteigs tos, kuri ir strādājuÅ”i ar lielām Python kodu bāzēm. Mums bija simtiem moduļu, no kuriem katrs netieÅ”i importēja visus pārējos. Ja jebkurÅ” fails cikla importos tika mainÄ«ts, mypy nācās apstrādāt visus failus, kas bija ietverti Å”ajā ciklā, un bieži vēl arÄ« jebkādus moduļus, kas importēja moduļus no Ŕī cikla. Viens no Å”iem cikliem bija slavenais ā€žatkarÄ«bu mezglsā€œ, kas radÄ«ja daudz nepatikÅ”anu Dropbox. Reiz Ŕī struktÅ«ra ietvēra vairākus simtus moduļu, un to importēja, tieÅ”i vai netieÅ”i, daudzi testi, kā arÄ« to izmantoja produkcijas kodā.

Mēs apsvēra iespēju ā€žatraisÄ«tā€ cikliskās atkarÄ«bas, taču mums nebija resursu to izdarÄ«t. Tur bija pārāk daudz koda, ar kuru mēs nebijām pazÄ«stami. Galu galā mēs izvēlējāmies alternatÄ«vu pieeju. Mēs nolēmām panākt, lai mypy darbotos ātri pat tad, ja ir ā€žatkarÄ«bu mezgliā€. Mēs sasniedzām Å”o mērÄ·i ar mypy dmeonu. Dmeons ir servera process, kas Ä«steno divas interesantas iespējas. Pirmkārt – tas saglabā visus kodu bāzes datus atmiņā. Tas nozÄ«mē, ka katru reizi, palaižot mypy, nav jāielādē keÅ”atmiņā esoÅ”ie dati, kas saistÄ«ti ar tÅ«kstoÅ”iem importēto atkarÄ«bu. Otrkārt – tas rÅ«pÄ«gi analÄ«zē attiecÄ«bas starp funkcijām un citām entÄ«tijām sÄ«ku struktÅ«ru lÄ«menÄ«. Piemēram, ja funkcija foo izsauc funkciju bar, tad ir atkarÄ«ba foo no bar. Kad fails tiek mainÄ«ts, dmeons vispirms atseviŔķi apstrādā tikai mainÄ«to failu. Pēc tam tas aplÅ«ko Ŕī faila izmaiņas, kas ir redzamas no ārpuses, piemēram, mainÄ«tās funkciju paraksti. Dmeons izmanto detalizētu informāciju par importiem tikai, lai pārbaudÄ«tu tās funkcijas, kuras faktiski izmanto mainÄ«to funkciju. Parasti Ŕādas pieejas gadÄ«jumā jāpārbauda salÄ«dzinoÅ”i maz funkciju.

Visu Å”o Ä«stenoÅ”ana bija sarežģīta, jo sākotnējā mypy realizācija bija ļoti vērsta uz viena faila apstrādi vienlaicÄ«gi. Mums nācās saskarties ar daudzām robežsituācijām, kuru raÅ”anās prasÄ«ja atkārtotas pārbaudes, kad kodā tika veiktas izmaiņas. Piemēram, tas notiek, kad klasei tiek pieŔķirta jauna pamatklase. Pēc tam, kad mēs izdarÄ«jām to, ko vēlējāmies, mēs spējam samazināt vairuma inkrementālo pārbaudes laiku lÄ«dz dažām sekundēm. Mums tas Ŕķita liels sasniegums.

Vēl lielāka veiktspēja!

Kopā ar attālināto keÅ”atmiņu, par kuru es iepriekÅ” runāju, mypy demonstrators praktiski pilnÄ«bā atrisināja problēmas, kas radās, kad programmētājs bieži veic tipu pārbaudi, veicot izmaiņas nelielā failu skaitā. Tomēr sistēmas veiktspēja, visneizdevÄ«gākajā tās izmantoÅ”anas scenārijā, joprojām bija tālu no optimālās. TÄ«ra mypy palaiÅ”ana varēja aizņemt vairāk nekā 15 minÅ«tes. Un tas bija daudz vairāk, nekā mÅ«s apmierinātu. Katru nedēļu situācija kļuva aizvien sliktāka, jo programmētāji turpināja rakstÄ«t jaunu kodu un pievienot anotācijas esoÅ”ajam kodam. MÅ«su lietotāji joprojām ilgojās pēc lielākas veiktspējas, un mēs ar prieku bijām gatavi viņiem palÄ«dzēt.

Mēs nolēmām atgriezties pie vienas no agrākajām idejām par mypy. Proti, par Python koda pārvērÅ”anu C kodā. Eksperimenti ar Cython (kas ir sistēma, kas ļauj tulkot Python rakstÄ«tu kodu C kodā) mums deva maz redzamu uzlabojumu, tādēļ mēs nolēmām atdzÄ«vināt ideju par sava kompilatora rakstīŔanu. Tā kā mypy kodu bāze (rakstÄ«ta Python) jau ietvēra visas nepiecieÅ”amās tipu anotācijas, mums likās vērts mēģināt izmantot Ŕīs anotācijas, lai paātrinātu sistēmas darbÄ«bu. Es ātri izveidoju prototipu, lai pārbaudÄ«tu Å”o ideju. Tas rādÄ«ja vairāk nekā desmitkārtÄ«gu veiktspējas pieaugumu dažādos mikro-benchmarks. MÅ«su ideja bija kompilēt Python moduļus C moduļos, izmantojot Cython, un pārvērst tipu anotācijas par tipu pārbaudēm, kas tiek veiktas programmas izpildes laikā (parasti tipu anotācijas programmā tiek ignorētas izpildes laikā un tiek izmantotas tikai tipu pārbaudes sistēmās). Mēs faktiski plānojām pārvērst mypy realizāciju no Python uz valodu, kas ir statiski tipizēta, kas izskatÄ«tos (un lielākoties darbotos) tieÅ”i tāpat kā Python. (Å is interlangu Ä£enēzes variants kļuva par kaut kādu tradÄ«ciju mypy projektā. Sākotnējā mypy realizācija tika rakstÄ«ta Alore, pēc tam bija sintaktisks hibrÄ«ds starp Java un Python).

Orientācija uz CPython paplaÅ”inājumu API bija atslēga, lai nezaudētu projekta pārvaldÄ«bas iespējas. Mums nebija jārealizē virtuālā maŔīna vai jebkuras bibliotēkas, kas bija nepiecieÅ”amas mypy. Turklāt mēs joprojām varējām piekļūt visai Python ekosistēmai, visi rÄ«ki (piemēram, pytest) bÅ«tu pieejami. Tas nozÄ«mēja, ka mēs varējām turpināt izmantot interpretēto Python kodu izstrādes laikā, kas ļautu mums turpināt darbu, izmantojot ļoti ātru kodu labojumu un testēŔanas shēmu, nevis gaidÄ«t koda kompilāciju. IzskatÄ«jās, ka mums lieliski izdodas, tā sakot, sēdēt uz diviem krēsliem, un mēs to baudÄ«jām.

Kompilators, ko mēs nosaucām par mypyc (jo tas kā frontend izmanto mypy tipu analÄ«zei), izrādÄ«jās samērā veiksmÄ«gs projekts. Kopumā mēs sasniedzām aptuveni 4 reizes ātrākus mypy izsaukumus bez keÅ”oÅ”anas izmantoÅ”anas. Mypyc projekta kodola izstrādei nelielai komandai, kurā ietilpa Maikls Salivans, Ivans Levkivskis, HjÅ« Hens un es, bija nepiecieÅ”ami apmēram 4 kalendāra mēneÅ”i. Å is darba apjoms bija daudz mazāks nekā tas, kas bÅ«tu vajadzÄ«gs, lai pārrakstÄ«tu mypy, piemēram, uz C++ vai Go. Un arÄ« izmaiņu skaits, ko mums vajadzēja ieviest projektā, bija krietni mazāks, nekā tas bÅ«tu nepiecieÅ”ams, pārrakstot to citā valodā. Mēs, turklāt, cerējām, ka varēsim mypyc aizvest lÄ«dz tādam lÄ«menim, lai to varētu izmantot, lai kompilētu un paātrinātu savu kodu, arÄ« citi Dropbox programmētāji.

Lai sasniegtu Ŕādu veiktspēju, mums nācās pielietot dažus interesantus inženiertehniskus risinājumus. Piemēram, kompilators spēj paātrināt daudzas operācijas, izmantojot ātrās zemā lÄ«meņa C konstrukcijas. Piemēram, izsaukums uz kompilētu funkciju tiek pārvērsts par izsaukumu uz C funkciju. Un tāds izsaukums tiek veikts daudz ātrāk nekā interpretētā funkcija. Dažas operācijas, tādas kā meklēŔana vārdnÄ«cās, joprojām palika pie parastajiem C-API izsaukumiem no CPython, kas pēc kompilācijas izrādÄ«jās tikai nedaudz ātrākas. Mēs spējam atbrÄ«voties no papildu slodzes uz sistēmu, ko radÄ«ja interpretācija, taču Å”ajā gadÄ«jumā tas sniedza tikai nelielu peļņu veiktspējā.

Lai identificētu visizplatÄ«tākās "lēnās" operācijas, mēs veicām koda profilēŔanu. Ar iegÅ«tajiem datiem mēs centāmies vai nu uzlabot mypyc, lai tas Ä£enerētu ātrāku C kodu Ŕādām operācijām, vai pārrakstÄ«t atbilstoÅ”o Python kodu, izmantojot ātrākas operācijas (dažreiz mums vienkārÅ”i nebija pietiekami vienkārÅ”a risinājuma Å”ai vai tai problēmai). Python koda pārrakstīŔana bieži izrādÄ«jās vieglāks risinājums problēmai nekā automātiska tā paÅ”a transformācijas Ä«stenoÅ”ana kompilatorā. Ilgtermiņā mēs vēlējāmies automatizēt daudzas no Ŕīm transformācijām, bet tajā brÄ«dÄ« mēs fokusējāmies uz to, lai ar minimālu piepÅ«li paātrinātu mypy. Un, virzoties uz Å”o mērÄ·i, mēs izgriezām dažus stÅ«rīŔus.

Turpinājums sekos…

CienÄ«jamie lasÄ«tāji! Kādas bija jÅ«su sajÅ«tas par mypy projektu, kad uzzinājāt par tā pastāvēŔanu?

CeļŔ uz 4 miljonu rindu Python koda tipu pārbaudi. Otrā daļa
CeļŔ uz 4 miljonu rindu Python koda tipu pārbaudi. Otrā daļa

Avots: habr.com

Iegādājieties uzticamu tÄ«mekļa hostingu ar DDoS aizsardzÄ«bu, VPS VDS serveriem šŸ”„ Iegādājieties uzticamu tÄ«mekļa hostingu ar DDoS aizsardzÄ«bu, VPS VDS serveriem | ProHoster