Python կոդի 4 միլիոն շարքիประเภทների ստուգման ճանապարհը։ Հրապարակման 2

Այսօր հրատարակում ենք Dropbox-ում մի քանի միլիոն շարքի Python կոդի տեսակների վերահսկման ՝ ավտոմատացված համակարգի վերաբերյալ նյութի թարգմանության երկրորդ մասը։

Python կոդի 4 միլիոն շարքիประเภทների ստուգման ճանապարհը։ Հրապարակման 2

Կարդալ առաջին մասը

Ավտոմատացված տեսակների պաշտոնական աջակցություն (PEP 484)

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

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

Մեր մոտեցումները կարելի էր դիտարկել երկու կողմերից։ Նախ, մենք հույս ունեինք, որ Python-ի ամբողջ էկոհամակարգը կարող էր ընդունել ընդհանուր մոտեցում տեսակները խորհրդատվություն (type hints) օգտագործելու վերաբերյալ։ Դա, հաշվի առնելով հնարավոր ռիսկերը, կգերազանցի համատեղ չհամապատասխանող մոտեցումների կիրառմանը։ Երկրորդ, մենք ցանկանում էինք բաց քննարկել տեսակների_annotations_-ի մեխանիզմները Python համայնքի բազմաթիվ ներկայացուցիչների հետ։ جز جز جز جز الجز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز جز

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

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

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

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

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

Սա արագ և բնական ստուգման համակարգի ներդրման ժամանակահատվակը էր Dropbox-ում: Մեր մոտ, 2016 թվականի վերջի դրությամբ, արդեն մոտ 420000 Python կոդի տող էր առկա տեսակետներով: Ա daudz օգտատերեր ուշադրություն դարձրեցին տեսակետների ստուգմանը: Dropbox-ում mypy-ն ավելի ու ավելի շատ ծրագրավորողների թիմերի կողմից օգտագործվում էր:

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

Ավելացված արտադրողականություն!

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

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

Այս ամենի իրականացումը դժվար բան էր, քանի որ mypy-ի սկզբնական իրականացումը կտրուկ կենտրոնացած էր մեկ ֆայլի մշակման վրա։ Մենք ստիպված ենք եղել բախվել բազմաթիվ սահմանային իրավիճակների, որոնք պահանջում էին կրկնակի ստուգումներ այն դեպքերում, երբ կոդում ինչ-որ բան փոխվում էր։ Օրինակ, նման բան տեղի է ունենում այն ժամանակ, երբ դասին назначают նոր գարնային դաս։ Երբ մենք արել ենք այն, ինչ ուզում էինք, կարողացել ենք նվազեցնել մեծամասնության ինկրեմենտալ ստուգումների կատարումը մի քանի վայրկյանների։ Դա մեզ թվում էր մեծ հաղթանակ։

Նորից ավելի արդյունավետություն!

Ուզովի հեռակառավարման կեշավորման հետ, որի մասին խնդոներ էի խոսել ավելի վերևում, mypy-ի դեոնը գործնականում ամբողջովին լուծել է այն խնդիրները, որոնք առաջանում են, երբ ծրագրավորողը հաճախ запускает տեսակավորման ստուգում, կատարելով փոփոխություններ փոքր քանակությամբ ֆայլերում։ Սակայն, գործառնական համակարգի արդյունավետությունն ամենաանհարմար օգտագործման դեպքում դեռևս հեռու էր օպտիմալ դառնալուց։ mypy-ի մաքուր մեկնարկը կարող էր տևել ավելի քան 15 րոպե։ Այն շատ ավելի էր, քան մեզ հարմար կլիներ։ Յուրաքանչյուր շաբաթ իրավիճակը դարձել էր ավելի վատ, քանի որ ծրագրավորողները շարունակում էին գրել նոր կոդ և ավելացնել anotаciйa-ներ գոյություն ունենող կոդի։ մեր օգտվողները դեռ ցանկանում էին ավելի մեծ արդյունավետություն, իսկ մենք հաճությամբ պատրաստ էինք գնալ նրանց հանդիպել։

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

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

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

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

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

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

Հարգելի ընթերողներ! Ուզում եմ իմանալ, ինչպիսի տպավորություն թողեց mypy նախագծի ի հայտ գալուց հետո ձեզ վրա:

Python կոդի 4 միլիոն շարքիประเภทների ստուգման ճանապարհը։ Հրապարակման 2
Python կոդի 4 միլիոն շարքիประเภทների ստուգման ճանապարհը։ Հրապարակման 2

Ընտանիք: habr.com

Գնել հուսալի հյուրընկալում DDoS պաշտպանությամբ, VPS VDS սերվերներով 🔥 Գնել հուսալի հյուրընկալում DDoS պաշտպանությամբ, VPS VDS սերվերներով | ProHoster