Python կոդի 4 միլիոն տողերի տիպի ստուգման ուղին: Մաս 2

Այսօր մենք հրապարակում ենք նյութի թարգմանության երկրորդ մասը այն մասին, թե ինչպես է Dropbox-ը կազմակերպել Python կոդի մի քանի միլիոն տողերի տիպի կառավարումը։

Python կոդի 4 միլիոն տողերի տիպի ստուգման ուղին: Մաս 2

Կարդացեք առաջին մասը

Պաշտոնական տիպի աջակցություն (PEP 484)

Մենք անցկացրեցինք մեր առաջին լուրջ փորձերը mypy-ի հետ Dropbox-ում Hack Week 2014-ի ընթացքում: Hack Week-ը մեկշաբաթյա միջոցառում է, որը կազմակերպվում է Dropbox-ի կողմից: Այս ընթացքում աշխատակիցները կարող են աշխատել այն ամենի վրա, ինչ ցանկանում են: Dropbox-ի ամենահայտնի տեխնոլոգիական նախագծերից մի քանիսը սկսվել են նման միջոցառումների ժամանակ: Այս փորձի արդյունքում մենք եզրակացրինք, որ mypy-ը խոստումնալից տեսք ունի, թեև նախագիծը դեռ պատրաստ չէ լայնածավալ օգտագործման համար։

Այն ժամանակ «Python» տեսակի ակնարկային համակարգերի ստանդարտացման գաղափարը օդում էր: Ինչպես ասացի, Python 3.0-ից ի վեր հնարավոր էր օգտագործել տիպային անոտացիաներ ֆունկցիաների համար, բայց դրանք պարզապես կամայական արտահայտություններ էին, առանց սահմանված շարահյուսության և իմաստաբանության: Ծրագրի կատարման ընթացքում այս ծանոթագրությունները, մեծ մասամբ, պարզապես անտեսվել են: Hack Week-ից հետո մենք սկսեցինք աշխատել իմաստաբանության ստանդարտացման վրա: Այս աշխատանքը հանգեցրեց առաջացմանը PEP 484 (Գվիդո վան Ռոսումը, Լուկաշ Լանգան և ես համագործակցել ենք այս փաստաթղթի շուրջ):

Մեր շարժառիթները կարելի էր դիտարկել երկու կողմից. Նախ, մենք հույս ունեինք, որ Python-ի ամբողջ էկոհամակարգը կարող է ընդհանուր մոտեցում որդեգրել տիպային ակնարկներ օգտագործելու համար (տերմին, որն օգտագործվում է Python-ում որպես «տիպի անոտացիաների» համարժեք): Սա, հաշվի առնելով հնարավոր ռիսկերը, ավելի լավ կլիներ, քան փոխադարձաբար անհամատեղելի բազմաթիվ մոտեցումներ կիրառելը: Երկրորդ, մենք ցանկանում էինք բաց քննարկել տիպի անոտացիայի մեխանիզմները Python համայնքի շատ անդամների հետ: Այս ցանկությունը մասամբ թելադրված էր նրանով, որ Python ծրագրավորողների լայն զանգվածների աչքում մենք չէինք ցանկանա լեզվի հիմնական գաղափարներից «հավատուրացների» նմանվել: Դա դինամիկ տպագրված լեզու է, որը հայտնի է որպես «բադ տպում»։ Համայնքում հենց սկզբում չէր կարող չառաջանալ որոշակի կասկածելի վերաբերմունք ստատիկ մուտքագրման գաղափարի նկատմամբ։ Բայց այդ տրամադրությունն ի վերջո թուլացավ այն բանից հետո, երբ պարզ դարձավ, որ ստատիկ մուտքագրումը պարտադիր չի լինելու (և այն բանից հետո, երբ մարդիկ հասկացան, որ դա իրականում օգտակար է):

Տիպի ակնարկի շարահյուսությունը, որն ի վերջո ընդունվեց, շատ նման էր այն ժամանակին, ինչ աջակցում էր mypy-ին: PEP 484-ը թողարկվել է Python 3.5-ով 2015 թվականին: Python-ն այլևս դինամիկ տպագրվող լեզու չէր: Ինձ դուր է գալիս այս իրադարձության մասին պատկերացնել որպես Python-ի պատմության մեջ կարևոր իրադարձություն:

Միգրացիայի սկիզբ

2015 թվականի վերջին Dropbox-ը ստեղծեց երեք հոգուց բաղկացած թիմ՝ mypy-ի վրա աշխատելու համար։ Նրանց թվում էին Գվիդո վան Ռոսումը, Գրեգ Փրայսը և Դեյվիդ Ֆիշերը: Այդ պահից իրավիճակը սկսեց չափազանց արագ զարգանալ։ Mypy-ի աճի առաջին խոչընդոտը կատարումն էր: Ինչպես վերևում ակնարկեցի, նախագծի սկզբնական օրերին ես մտածում էի mypy-ի իրականացումը C-ով թարգմանելու մասին, բայց այս գաղափարն առայժմ դուրս է մնացել ցանկից: Մենք խրված էինք համակարգը գործարկելու մեջ՝ օգտագործելով CPython թարգմանիչը, որը բավականաչափ արագ չէ mypy-ի նման գործիքների համար: (PyPy նախագիծը, Python-ի այլընտրանքային իրականացումը JIT կոմպիլյատորով, նույնպես մեզ չօգնեց):

Բարեբախտաբար, այստեղ մեզ օգնության են հասել որոշ ալգորիթմական բարելավումներ: Առաջին հզոր «արագացուցիչը» աստիճանական ստուգման իրականացումն էր։ Այս բարելավման գաղափարը պարզ էր. եթե մոդուլի բոլոր կախվածությունները չեն փոխվել mypy-ի նախորդ գործարկումից հետո, ապա մենք կարող ենք օգտագործել նախորդ գործարկման ընթացքում քեշավորված տվյալները՝ կախվածությունների հետ աշխատելիս: Մեզ անհրաժեշտ էր միայն փոփոխված ֆայլերի և դրանցից կախված ֆայլերի տիպերի ստուգում: Mypy-ը նույնիսկ մի փոքր առաջ գնաց. եթե որևէ մոդուլի արտաքին ինտերֆեյսը չփոխվեց, mypy-ը ենթադրեց, որ այս մոդուլը ներմուծած մյուս մոդուլները նորից ստուգման կարիք չունեն:

Աճող ստուգումը մեզ շատ է օգնել գոյություն ունեցող կոդի մեծ քանակի ծանոթագրման ժամանակ: Բանն այն է, որ այս գործընթացը սովորաբար ներառում է mypy-ի բազմաթիվ կրկնվող գործարկումներ, քանի որ ծանոթագրությունները աստիճանաբար ավելացվում են կոդի մեջ և աստիճանաբար բարելավվում: Mypy-ի առաջին գործարկումը դեռ շատ դանդաղ էր, քանի որ այն ուներ ստուգելու շատ կախվածություններ: Այնուհետև իրավիճակը բարելավելու համար մենք ներդրեցինք հեռավոր քեշավորման մեխանիզմ։ Եթե ​​mypy-ը հայտնաբերում է, որ տեղական քեշը, ամենայն հավանականությամբ, հնացած է, այն ներբեռնում է ընթացիկ քեշի նկարը ամբողջ կոդի բազայի համար կենտրոնացված պահոցից: Այնուհետև այն կատարում է աստիճանական ստուգում՝ օգտագործելով այս նկարը: Սա մեզ ևս մեկ մեծ քայլ տարավ mypy-ի կատարողականությունը բարձրացնելու ուղղությամբ:

Սա Dropbox-ում տիպերի ստուգման արագ և բնական ընդունման շրջան էր: 2016 թվականի վերջում մենք արդեն ունեինք մոտավորապես 420000 տող Python կոդ՝ տիպի ծանոթագրություններով։ Շատ օգտատերեր ոգևորված էին տիպերի ստուգմամբ: Ավելի ու ավելի շատ մշակող թիմեր օգտագործում էին Dropbox mypy-ը:

Այն ժամանակ ամեն ինչ լավ էր թվում, բայց մենք դեռ շատ անելիքներ ունեինք։ Մենք սկսեցինք պարբերաբար ներքին օգտատերերի հարցումներ իրականացնել՝ ծրագրի խնդրահարույց ոլորտները բացահայտելու և հասկանալու համար, թե ինչ հարցեր են պետք լուծել առաջին հերթին (այս պրակտիկան դեռևս կիրառվում է ընկերությունում այսօր): Ամենակարևորը, ինչպես պարզ դարձավ, երկու խնդիր էր. Նախ, մեզ անհրաժեշտ էր ծածկագրի ավելի շատ տեսակի ծածկույթ, երկրորդը, մեզ անհրաժեշտ էր mypy-ն ավելի արագ աշխատելու համար: Միանգամայն պարզ էր, որ mypy-ն արագացնելու և այն ընկերության նախագծերում ներդնելու մեր աշխատանքը դեռ հեռու էր ավարտից: Մենք, լիովին գիտակցելով այս երկու խնդիրների կարևորությունը, ձեռնամուխ եղանք դրանց լուծմանը։

Ավելի շատ արտադրողականություն:

Աճող ստուգումները mypy-ին ավելի արագ դարձրին, բայց գործիքը դեռ բավականաչափ արագ չէր: Շատ աստիճանական ստուգումներ տևեցին մոտ մեկ րոպե: Սրա պատճառը ցիկլային ներմուծումն էր։ Սա, հավանաբար, չի զարմացնի որևէ մեկին, ով աշխատել է Python-ով գրված խոշոր կոդերի բազաների հետ: Մենք ունեինք հարյուրավոր մոդուլների հավաքածուներ, որոնցից յուրաքանչյուրն անուղղակիորեն ներմուծեց բոլոր մյուսները: Եթե ​​ներմուծման հանգույցում որևէ ֆայլ փոխվեր, mypy-ը պետք է մշակեր այդ օղակի բոլոր ֆայլերը, և հաճախ ցանկացած մոդուլ, որը մոդուլներ ներմուծեց այդ օղակից: Այդպիսի շրջաններից մեկը տխրահռչակ «կախվածության խճճվածությունն» էր, որը շատ դժվարություններ առաջացրեց Dropbox-ում: Երբ այս կառույցը պարունակում էր մի քանի հարյուր մոդուլ, մինչդեռ այն ներմուծվում էր, ուղղակիորեն կամ անուղղակիորեն, բազմաթիվ թեստեր, այն նաև օգտագործվում էր արտադրության կոդում:

Մենք դիտարկել ենք շրջանաձև կախվածությունները «խճճելու» հնարավորությունը, սակայն դրա համար ռեսուրս չենք ունեցել։ Չափազանց շատ ծածկագիր կար, որին մենք ծանոթ չէինք: Արդյունքում մենք հանդես եկանք այլընտրանքային մոտեցմամբ։ Մենք որոշեցինք արագացնել mypy-ը նույնիսկ «կախվածության խճճվածությունների» առկայության դեպքում: Մենք հասանք այս նպատակին՝ օգտագործելով mypy daemon-ը: Դեյմոնը սերվերի գործընթաց է, որն իրականացնում է երկու հետաքրքիր հնարավորություններ: Նախ, այն հիշողության մեջ պահում է տեղեկատվությունը ամբողջ կոդի բազայի մասին: Սա նշանակում է, որ ամեն անգամ, երբ դուք գործարկում եք mypy-ը, դուք ստիպված չեք լինի բեռնել քեշավորված տվյալները՝ կապված հազարավոր ներմուծված կախվածությունների հետ: Երկրորդ, նա ուշադիր, փոքր կառուցվածքային միավորների մակարդակով, վերլուծում է կախվածությունը գործառույթների և այլ սուբյեկտների միջև: Օրինակ, եթե ֆունկցիան foo կանչում է ֆունկցիա bar, ապա կա կախվածություն foo - ից bar. Երբ ֆայլը փոխվում է, դեյմոնը նախ, մեկուսացված, մշակում է միայն փոխված ֆայլը: Այնուհետև այն դիտում է այդ ֆայլի արտաքին տեսանելի փոփոխությունները, ինչպիսիք են փոխված գործառույթի ստորագրությունները: Դեյմոնն օգտագործում է ներմուծման մասին մանրամասն տեղեկատվություն միայն այն գործառույթները կրկնակի ստուգելու համար, որոնք իրականում օգտագործում են փոփոխված ֆունկցիան։ Սովորաբար, այս մոտեցմամբ դուք պետք է ստուգեք շատ քիչ գործառույթներ:

Այս ամենի իրականացումը հեշտ չէր, քանի որ նախնական mypy իրականացումը մեծապես կենտրոնացած էր միաժամանակ մեկ ֆայլի մշակման վրա: Մենք ստիպված էինք առնչվել բազմաթիվ սահմանային իրավիճակների հետ, որոնց առաջացումը պահանջում էր կրկնակի ստուգումներ այն դեպքերում, երբ օրենսգրքում ինչ-որ բան փոխվեց: Օրինակ, դա տեղի է ունենում, երբ դասին վերագրվում է նոր բազային դաս: Երբ մենք արեցինք այն, ինչ ուզում էինք, մենք կարողացանք կրճատել լրացուցիչ ստուգումների մեծ մասի կատարման ժամանակը մինչև ընդամենը մի քանի վայրկյան: Սա մեզ մեծ հաղթանակ էր թվում։

Նույնիսկ ավելի արտադրողականություն:

Հեռավոր քեշավորման հետ միասին, որը ես քննարկեցի վերևում, mypy daemon-ը գրեթե ամբողջությամբ լուծեց այն խնդիրները, որոնք առաջանում են, երբ ծրագրավորողը հաճախակի ստուգում է տիպերը՝ փոփոխություններ կատարելով փոքր թվով ֆայլերում: Այնուամենայնիվ, համակարգի կատարողականը ամենաանբարենպաստ օգտագործման դեպքում դեռ հեռու էր օպտիմալից: Mypy-ի մաքուր գործարկումը կարող է տևել ավելի քան 15 րոպե: Եվ սա շատ ավելին էր, քան մենք ուրախ կլինեինք: Ամեն շաբաթ իրավիճակը վատանում էր, քանի որ ծրագրավորողները շարունակում էին նոր կոդ գրել և ծանոթագրություններ ավելացնել առկա կոդի վրա: Մեր օգտատերերը դեռ քաղցած էին ավելի շատ կատարողականության, բայց մենք ուրախ էինք հանդիպել նրանց կես ճանապարհին:

Մենք որոշեցինք վերադառնալ mypy-ի վերաբերյալ նախկին գաղափարներից մեկին: Մասնավորապես, Python կոդը փոխարկելու համար C կոդի: Cython-ի հետ փորձարկումը (համակարգ, որը թույլ է տալիս թարգմանել Python-ով գրված կոդը C կոդով) մեզ տեսանելի արագություն չտվեց, ուստի մենք որոշեցինք վերակենդանացնել մեր սեփական կոմպիլյատորը գրելու գաղափարը: Քանի որ mypy կոդերի բազան (գրված է Python-ով) արդեն պարունակում էր բոլոր անհրաժեշտ տեսակի ծանոթագրությունները, մենք մտածեցինք, որ արժե փորձել օգտագործել այդ ծանոթագրությունները՝ համակարգը արագացնելու համար: Ես արագ ստեղծեցի նախատիպ այս գաղափարը փորձարկելու համար: Այն ցույց է տվել կատարողականի ավելի քան 10 անգամ աճ տարբեր միկրո հենանիշների վրա: Մեր գաղափարն էր կազմել Python մոդուլները C մոդուլների մեջ Cython-ի միջոցով և տիպի ծանոթագրությունները վերածել գործարկման ժամանակի տիպի ստուգումների (սովորաբար տիպի ծանոթագրությունները անտեսվում են գործարկման ժամանակ և օգտագործվում են միայն տիպերի ստուգման համակարգերի կողմից): Մենք իրականում նախատեսում էինք թարգմանել mypy-ի իրականացումը Python-ից մի լեզվի, որը նախագծված էր ստատիկ տպագրման համար, որը նման կլինի Python-ին (և մեծ մասամբ կաշխատի): (Այս տեսակի միջլեզվային միգրացիան դարձել է mypy նախագծի ավանդույթը: Mypy-ի սկզբնական իրականացումը գրվել է Alore-ով, այնուհետև եղել է Java-ի և Python-ի շարահյուսական հիբրիդ):

CPython ընդլայնման API-ի վրա կենտրոնանալը առանցքային էր նախագծի կառավարման հնարավորությունները չկորցնելու համար: Մենք կարիք չունեինք ներդրելու վիրտուալ մեքենա կամ որևէ գրադարան, որն անհրաժեշտ էր mypy-ին: Բացի այդ, մենք դեռ մուտք կունենայինք Python-ի ամբողջ էկոհամակարգին և բոլոր գործիքներին (օրինակ՝ pytest): Սա նշանակում էր, որ մենք կարող էինք շարունակել օգտագործել մեկնաբանված Python կոդը մշակման ընթացքում՝ թույլ տալով մեզ շարունակել աշխատել կոդի փոփոխությունները կատարելու և այն փորձարկելու շատ արագ օրինաչափությամբ, այլ ոչ թե սպասել, որ կոդը կազմվի: Թվում էր, թե մենք, այսպես ասած, երկու աթոռի վրա նստած մեծ աշխատանք էինք կատարում, և մեզ դա դուր եկավ:

Կոմպիլյատորը, որը մենք անվանեցինք mypyc (քանի որ այն օգտագործում է mypy-ը որպես տիպերի վերլուծության ճակատ), պարզվեց, որ շատ հաջող նախագիծ է։ Ընդհանուր առմամբ, մենք հասել ենք մոտավորապես 4x արագացման հաճախակի mypy-ի գործարկումների համար՝ առանց քեշավորման: Mypyc նախագծի առանցքը մշակելու համար Մայքլ Սալիվանի, Իվան Լևկիվսկու, Հյու Հանի և ինձանից բաղկացած մի փոքրիկ թիմ պահանջվեց մոտ 4 օրացուցային ամիս: Աշխատանքի այս ծավալը շատ ավելի փոքր էր, քան անհրաժեշտ կլիներ mypy-ը վերագրելու համար, օրինակ՝ C++ կամ Go-ում: Եվ մենք պետք է շատ ավելի քիչ փոփոխություններ կատարեինք նախագծում, քան պետք է անեինք այն այլ լեզվով վերաշարադրելիս: Մենք նաև հույս ունեինք, որ կարող ենք mypyc-ը հասցնել այնպիսի մակարդակի, որ այլ Dropbox ծրագրավորողներ կարողանան օգտագործել այն իրենց ծածկագիրը կազմելու և արագացնելու համար:

Արդյունավետության այս մակարդակին հասնելու համար մենք պետք է կիրառեինք մի քանի հետաքրքիր ինժեներական լուծումներ: Այսպիսով, կոմպիլյատորը կարող է արագացնել բազմաթիվ գործողություններ՝ օգտագործելով արագ, ցածր մակարդակի C կոնստրուկցիաներ: Օրինակ, կոմպիլացված ֆունկցիայի կանչը թարգմանվում է C ֆունկցիայի կանչի: Եվ նման զանգը շատ ավելի արագ է, քան մեկնաբանված ֆունկցիա կանչելը: Որոշ գործողություններ, ինչպիսիք են բառարանների որոնումները, դեռևս ներառում էին CPython-ից C-API կանոնավոր զանգերի օգտագործումը, որոնք կազմվելիս միայն փոքր-ինչ ավելի արագ էին: Մենք կարողացանք վերացնել մեկնաբանության միջոցով ստեղծված համակարգի վրա լրացուցիչ բեռը, բայց սա այս դեպքում միայն փոքր շահույթ տվեց կատարողականի առումով:

Ամենատարածված «դանդաղ» գործողությունները բացահայտելու համար մենք կատարեցինք կոդի պրոֆիլավորում: Զինված այս տվյալներով՝ մենք փորձեցինք կամ կսմթել mypyc-ը, որպեսզի այն ավելի արագ C կոդ ստեղծի նման գործողությունների համար, կամ վերաշարադրի համապատասխան Python կոդը՝ օգտագործելով ավելի արագ գործողություններ (և երբեմն մենք պարզապես չունեինք բավականաչափ պարզ լուծում այդ կամ այլ խնդրի համար): . Python ծածկագրի վերագրանցումը հաճախ ավելի հեշտ լուծում էր խնդրին, քան կոմպիլյատորի կողմից նույն փոխակերպումն ավտոմատ կերպով կատարելը: Երկարաժամկետ հեռանկարում մենք ցանկանում էինք ավտոմատացնել այս փոխակերպումներից շատերը, բայց այն ժամանակ մենք կենտրոնացած էինք նվազագույն ջանքերով mypy-ի արագացման վրա: Եվ դեպի այս նպատակը շարժվելիս մենք կտրեցինք մի քանի անկյուն:

Շարունակելի…

Հարգելի ընթերցողներ: Ի՞նչ տպավորություններ ստացաք mypy նախագծից, երբ իմացաք դրա գոյության մասին:

Python կոդի 4 միլիոն տողերի տիպի ստուգման ուղին: Մաս 2
Python կոդի 4 միլիոն տողերի տիպի ստուգման ուղին: Մաս 2

Source: www.habr.com

Добавить комментарий