Y llwybr i deipio 4 miliwn o linellau o god Python. Rhan 2

Heddiw rydym yn cyhoeddi ail ran y cyfieithiad o ddeunydd am sut y trefnodd Dropbox reolaeth math ar gyfer sawl miliwn o linellau o god Python.

Y llwybr i deipio 4 miliwn o linellau o god Python. Rhan 2

Darllenwch ran un

Cymorth math swyddogol (PEP 484)

Fe wnaethom gynnal ein harbrofion difrifol cyntaf gyda mypy yn Dropbox yn ystod Wythnos Hacio 2014. Mae Wythnos Hacio yn ddigwyddiad wythnos a gynhelir gan Dropbox. Yn ystod y cyfnod hwn, gall gweithwyr weithio ar beth bynnag maen nhw ei eisiau! Dechreuodd rhai o brosiectau technoleg enwocaf Dropbox mewn digwyddiadau fel y rhain. O ganlyniad i'r arbrawf hwn, daethom i'r casgliad bod mypy yn edrych yn addawol, er nad yw'r prosiect yn barod i'w ddefnyddio'n eang eto.

Ar y pryd, roedd y syniad o safoni systemau awgrym o fath Python yn yr awyr. Fel y dywedais, ers Python 3.0 roedd yn bosibl defnyddio anodiadau teip ar gyfer swyddogaethau, ond mynegiadau mympwyol yn unig oedd y rhain, heb gystrawen a semanteg diffiniedig. Yn ystod gweithredu'r rhaglen, anwybyddwyd yr anodiadau hyn ar y cyfan. Ar ôl Wythnos Hacio, dechreuon ni weithio ar safoni semanteg. Arweiniodd y gwaith hwn at yr ymddangosiad PEP 484 (Cydweithiodd Guido van Rossum, Łukasz Langa a minnau ar y ddogfen hon).

Gellid edrych ar ein cymhellion o ddwy ochr. Yn gyntaf, roeddem yn gobeithio y gallai ecosystem gyfan Python fabwysiadu dull cyffredin o ddefnyddio awgrymiadau teip (term a ddefnyddir yn Python fel yr hyn sy'n cyfateb i "anodiadau math"). Byddai hyn, o ystyried y risgiau posibl, yn well na defnyddio llawer o ddulliau sy’n anghydnaws â’i gilydd. Yn ail, roeddem am drafod mecanweithiau anodi teip yn agored gyda llawer o aelodau o gymuned Python. Roedd yr awydd hwn yn cael ei bennu’n rhannol gan y ffaith na fyddem am edrych fel “apostates” o syniadau sylfaenol yr iaith yng ngolwg y llu eang o raglenwyr Python. Mae'n iaith wedi'i theipio'n ddeinamig, a elwir yn "deipio hwyaid". Yn y gymuned, ar y cychwyn cyntaf, ni allai agwedd braidd yn amheus tuag at y syniad o deipio statig helpu ond codi. Ond gwanhaodd y teimlad hwnnw yn y pen draw ar ôl iddi ddod yn amlwg nad oedd teipio statig yn mynd i fod yn orfodol (ac ar ôl i bobl sylweddoli ei fod yn ddefnyddiol mewn gwirionedd).

Roedd y gystrawen math awgrym a fabwysiadwyd yn y pen draw yn debyg iawn i'r hyn yr oedd mypy yn ei gefnogi ar y pryd. Rhyddhawyd PEP 484 gyda Python 3.5 yn 2015. Nid oedd Python bellach yn iaith wedi'i theipio'n ddeinamig yn unig. Rwy'n hoffi meddwl am y digwyddiad hwn fel carreg filltir arwyddocaol yn hanes Python.

Dechrau mudo

Ar ddiwedd 2015, creodd Dropbox dîm o dri o bobl i weithio ar mypy. Roeddent yn cynnwys Guido van Rossum, Greg Price a David Fisher. O'r eiliad honno ymlaen, dechreuodd y sefyllfa ddatblygu'n gyflym iawn. Y rhwystr cyntaf i dwf mypy oedd perfformiad. Fel yr awgrymais uchod, yn nyddiau cynnar y prosiect meddyliais am drosi'r gweithrediad mypy yn C, ond croeswyd y syniad hwn oddi ar y rhestr am y tro. Roeddem yn sownd â rhedeg y system gan ddefnyddio dehonglydd CPython, nad yw'n ddigon cyflym ar gyfer offer fel mypy. (Ni wnaeth y prosiect PyPy, gweithrediad Python amgen gyda chasglwr JIT, ein helpu chwaith.)

Yn ffodus, mae rhai gwelliannau algorithmig wedi dod i'n cymorth yma. Y “cyflymydd” pwerus cyntaf oedd gweithredu gwirio cynyddrannol. Roedd y syniad y tu ôl i'r gwelliant hwn yn syml: os nad yw holl ddibyniaethau'r modiwl wedi newid ers rhediad blaenorol mypy, yna gallwn ddefnyddio'r data a storiwyd yn ystod y rhediad blaenorol wrth weithio gyda dibyniaethau. Dim ond ar y ffeiliau a addaswyd ac ar y ffeiliau a oedd yn dibynnu arnynt yr oedd angen i ni wneud gwiriad math. Aeth Mypy hyd yn oed ychydig ymhellach: pe na bai rhyngwyneb allanol modiwl yn newid, cymerodd mypy nad oedd angen gwirio modiwlau eraill a fewnforiodd y modiwl hwn eto.

Mae gwirio cynyddrannol wedi bod o gymorth mawr i ni wrth anodi symiau mawr o god presennol. Y pwynt yw bod y broses hon fel arfer yn cynnwys llawer o rediadau iterus o mypy wrth i anodiadau gael eu hychwanegu'n raddol at y cod a'u gwella'n raddol. Roedd rhediad cyntaf mypy yn dal yn araf iawn oherwydd roedd ganddo lawer o ddibyniaethau i'w gwirio. Yna, i wella'r sefyllfa, fe wnaethom weithredu mecanwaith caching o bell. Os yw mypy yn canfod bod y storfa leol yn debygol o fod wedi dyddio, mae'n lawrlwytho'r ciplun storfa gyfredol ar gyfer y sylfaen cod gyfan o'r ystorfa ganolog. Yna mae'n cynnal gwiriad cynyddrannol gan ddefnyddio'r ciplun hwn. Mae hyn wedi cymryd un cam mawr arall tuag at gynyddu perfformiad mypy.

Roedd hwn yn gyfnod o fabwysiadu gwirio teip yn gyflym ac yn naturiol yn Dropbox. Erbyn diwedd 2016, roedd gennym eisoes tua 420000 o linellau o god Python gydag anodiadau math. Roedd llawer o ddefnyddwyr yn frwdfrydig ynghylch gwirio teip. Roedd mwy a mwy o dimau datblygu yn defnyddio Dropbox mypy.

Roedd popeth yn edrych yn dda bryd hynny, ond roedd gennym lawer i'w wneud o hyd. Dechreuon ni gynnal arolygon defnyddwyr mewnol cyfnodol er mwyn nodi meysydd problemus y prosiect a deall pa faterion sydd angen eu datrys yn gyntaf (mae'r arfer hwn yn dal i gael ei ddefnyddio yn y cwmni heddiw). Y pwysicaf, fel y daeth yn amlwg, oedd dwy dasg. Yn gyntaf, roedd angen mwy o sylw math o'r cod, yn ail, roedd angen mypy i weithio'n gyflymach. Roedd yn gwbl amlwg bod ein gwaith i gyflymu mypy a'i roi ar waith ym mhrosiectau cwmni yn dal i fod ymhell o fod wedi'i gwblhau. Rydym ni, yn gwbl ymwybodol o bwysigrwydd y ddwy dasg hyn, yn mynd ati i'w datrys.

Mwy o gynhyrchiant!

Gwnaeth gwiriadau cynyddrannol mypy yn gyflymach, ond nid oedd yr offeryn yn ddigon cyflym o hyd. Roedd llawer o wiriadau cynyddrannol yn para tua munud. Y rheswm am hyn oedd mewnforion cylchol. Mae'n debyg na fydd hyn yn synnu unrhyw un sydd wedi gweithio gyda chronfeydd codau mawr a ysgrifennwyd yn Python. Roedd gennym setiau o gannoedd o fodiwlau, pob un ohonynt yn mewnforio'r lleill i gyd yn anuniongyrchol. Pe bai unrhyw ffeil mewn dolen fewnforio yn cael ei newid, roedd yn rhaid i mypy brosesu'r holl ffeiliau yn y ddolen honno, ac yn aml unrhyw fodiwlau oedd yn mewnforio modiwlau o'r ddolen honno. Un cylch o'r fath oedd y “tangle dibyniaeth” enwog a achosodd lawer o drafferth yn Dropbox. Unwaith y bydd y strwythur hwn yn cynnwys cannoedd o fodiwlau, tra ei fod yn cael ei fewnforio, yn uniongyrchol neu'n anuniongyrchol, llawer o brofion, fe'i defnyddiwyd hefyd mewn cod cynhyrchu.

Gwnaethom ystyried y posibilrwydd o "ddatrys" dibyniaethau cylchol, ond nid oedd gennym yr adnoddau i wneud hynny. Roedd gormod o god nad oeddem yn gyfarwydd ag ef. O ganlyniad, fe wnaethom lunio dull amgen. Fe benderfynon ni wneud i mypy weithio'n gyflym hyd yn oed ym mhresenoldeb “tanglau dibyniaeth”. Cyflawnwyd y nod hwn gan ddefnyddio'r daemon mypy. Mae daemon yn broses gweinydd sy'n gweithredu dwy nodwedd ddiddorol. Yn gyntaf, mae'n storio gwybodaeth am y cod sylfaen cyfan yn y cof. Mae hyn yn golygu, bob tro y byddwch chi'n rhedeg mypy, nad oes rhaid i chi lwytho data wedi'i storio sy'n ymwneud â miloedd o ddibyniaethau a fewnforir. Yn ail, mae'n ofalus, ar lefel yr unedau strwythurol bach, yn dadansoddi'r dibyniaethau rhwng swyddogaethau ac endidau eraill. Er enghraifft, os yw'r swyddogaeth foo yn galw swyddogaeth bar, yna mae dibyniaeth foo o bar. Pan fydd ffeil yn newid, mae'r ellyll yn gyntaf, ar ei ben ei hun, yn prosesu'r ffeil sydd wedi'i newid yn unig. Yna mae'n edrych ar newidiadau y gellir eu gweld yn allanol i'r ffeil honno, megis llofnodion swyddogaeth wedi'u newid. Mae'r ellyll yn defnyddio gwybodaeth fanwl am fewnforion yn unig i wirio'r swyddogaethau hynny sy'n defnyddio'r swyddogaeth wedi'i haddasu mewn gwirionedd. Yn nodweddiadol, gyda'r dull hwn, mae'n rhaid i chi wirio ychydig iawn o swyddogaethau.

Nid oedd yn hawdd gweithredu hyn i gyd, gan fod y gweithrediad mypy gwreiddiol yn canolbwyntio'n drwm ar brosesu un ffeil ar y tro. Roedd yn rhaid i ni ddelio â llawer o sefyllfaoedd ffiniol, ac roedd angen cynnal gwiriadau dro ar ôl tro mewn achosion lle roedd rhywbeth yn newid yn y cod. Er enghraifft, mae hyn yn digwydd pan roddir dosbarth sylfaen newydd i ddosbarth. Unwaith y gwnaethom yr hyn yr oeddem ei eisiau, roeddem yn gallu lleihau amser gweithredu'r rhan fwyaf o wiriadau cynyddrannol i ychydig eiliadau yn unig. Roedd hyn yn ymddangos fel buddugoliaeth fawr i ni.

Hyd yn oed mwy o gynhyrchiant!

Ynghyd â'r caching anghysbell a drafodais uchod, roedd yr ellyll mypy bron yn llwyr ddatrys y problemau sy'n codi pan fydd rhaglennydd yn rhedeg gwirio math yn aml, gan wneud newidiadau i nifer fach o ffeiliau. Fodd bynnag, roedd perfformiad y system yn yr achos defnydd lleiaf ffafriol ymhell o fod yn optimaidd o hyd. Gallai cychwyn glân o mypy gymryd dros 15 munud. Ac roedd hyn yn llawer mwy nag y byddem wedi bod yn hapus ag ef. Bob wythnos aeth y sefyllfa'n waeth wrth i raglenwyr barhau i ysgrifennu cod newydd ac ychwanegu anodiadau i'r cod presennol. Roedd ein defnyddwyr yn dal yn newynog am fwy o berfformiad, ond roeddem yn hapus i gwrdd â nhw hanner ffordd.

Penderfynasom ddychwelyd at un o'r syniadau cynharach ynglŷn â mypy. Sef, i drosi cod Python yn god C. Ni roddodd arbrofi gyda Cython (system sy'n eich galluogi i gyfieithu cod a ysgrifennwyd yn Python i god C) unrhyw gyflymu gweladwy i ni, felly fe benderfynon ni adfywio'r syniad o ysgrifennu ein casglwr ein hunain. Gan fod y mypy codebase (a ysgrifennwyd yn Python) eisoes yn cynnwys yr holl anodiadau teip angenrheidiol, roeddem yn meddwl y byddai'n werth ceisio defnyddio'r anodiadau hyn i gyflymu'r system. Fe wnes i greu prototeip yn gyflym i brofi'r syniad hwn. Dangosodd gynnydd o fwy na 10 gwaith yn fwy mewn perfformiad ar amrywiol ficro-meincnodau. Ein syniad ni oedd llunio modiwlau Python i fodiwlau C gan ddefnyddio Cython, a throi anodiadau math yn wiriadau math amser rhedeg (fel arfer mae anodiadau teip yn cael eu hanwybyddu ar amser rhedeg ac yn cael eu defnyddio gan systemau gwirio math yn unig). Roeddem mewn gwirionedd yn bwriadu cyfieithu'r gweithrediad mypy o Python i iaith a ddyluniwyd i'w theipio'n statig, a fyddai'n edrych (ac, ar y cyfan, yn gweithio) yn union fel Python. (Mae'r math hwn o fudo traws-iaith wedi dod yn dipyn o draddodiad o'r prosiect mypy. Ysgrifennwyd y gweithrediad mypy gwreiddiol yn Alore, yna roedd hybrid cystrawenyddol o Java a Python).

Roedd canolbwyntio ar API estyniad CPython yn allweddol i beidio â cholli galluoedd rheoli prosiect. Nid oedd angen i ni weithredu peiriant rhithwir nac unrhyw lyfrgelloedd yr oedd eu hangen ar mypy. Yn ogystal, byddem yn dal i gael mynediad i'r ecosystem Python gyfan a'r holl offer (fel pytest). Roedd hyn yn golygu y gallem barhau i ddefnyddio cod Python wedi'i ddehongli yn ystod y datblygiad, gan ganiatáu i ni barhau i weithio gyda phatrwm cyflym iawn o wneud newidiadau cod a'i brofi, yn hytrach nag aros i'r cod lunio. Roedd yn edrych fel ein bod yn gwneud gwaith gwych o eistedd ar ddwy gadair, fel petai, ac roeddem wrth ein bodd.

Trodd y casglwr, y gwnaethom ei alw'n mypyc (gan ei fod yn defnyddio mypy fel pen blaen ar gyfer dadansoddi mathau), yn brosiect llwyddiannus iawn. Ar y cyfan, gwnaethom gyflawni tua 4x cyflymiad ar gyfer rhediadau mypy aml heb gelcio. Wrth ddatblygu craidd y prosiect mypyc cymerodd tîm bach o Michael Sullivan, Ivan Levkivsky, Hugh Hahn, a minnau tua 4 mis calendr. Roedd y swm hwn o waith yn llawer llai na'r hyn y byddai ei angen i ailysgrifennu mypy, er enghraifft, yn C++ neu Go. Ac roedd yn rhaid i ni wneud llawer llai o newidiadau i'r prosiect nag y byddai'n rhaid i ni ei wneud wrth ei ailysgrifennu mewn iaith arall. Roeddem hefyd yn gobeithio y gallem ddod â mypyc i'r fath lefel y gallai rhaglenwyr Dropbox eraill ei ddefnyddio i lunio a chyflymu eu cod.

Er mwyn cyflawni'r lefel hon o berfformiad, roedd yn rhaid i ni gymhwyso rhai atebion peirianneg diddorol. Felly, gall y casglwr gyflymu llawer o weithrediadau trwy ddefnyddio lluniadau cyflym, lefel isel C. Er enghraifft, mae galwad ffwythiant wedi'i chrynhoi yn cael ei throsi'n alwad ffwythiant C. Ac mae galwad o'r fath yn llawer cyflymach na galw swyddogaeth wedi'i dehongli. Roedd rhai gweithrediadau, megis chwilio geiriadur, yn dal i gynnwys defnyddio galwadau C-API rheolaidd gan CPython, a oedd ychydig yn gyflymach yn unig ar ôl eu llunio. Roeddem yn gallu dileu'r llwyth ychwanegol ar y system a grëwyd trwy ddehongli, ond yn yr achos hwn dim ond cynnydd bach a roddodd hyn o ran perfformiad.

Er mwyn nodi'r gweithrediadau “araf” mwyaf cyffredin, gwnaethom broffilio cod. Gyda'r data hwn, fe wnaethom geisio naill ai newid mypyc fel y byddai'n cynhyrchu cod C cyflymach ar gyfer gweithrediadau o'r fath, neu ailysgrifennu'r cod Python cyfatebol gan ddefnyddio gweithrediadau cyflymach (ac weithiau nid oedd gennym ateb digon syml ar gyfer y broblem honno neu broblem arall) . Roedd ailysgrifennu'r cod Python yn aml yn ateb haws i'r broblem na chael y casglwr yn perfformio'r un trawsnewidiad yn awtomatig. Yn y tymor hir, roeddem am awtomeiddio llawer o'r trawsnewidiadau hyn, ond ar y pryd roeddem yn canolbwyntio ar gyflymu mypy heb fawr o ymdrech. Ac wrth symud tuag at y nod hwn, rydym yn torri sawl cornel.

I'w barhau…

Annwyl ddarllenwyr! Beth oedd eich argraffiadau o'r prosiect mypy pan glywsoch chi am ei fodolaeth?

Y llwybr i deipio 4 miliwn o linellau o god Python. Rhan 2
Y llwybr i deipio 4 miliwn o linellau o god Python. Rhan 2

Ffynhonnell: hab.com

Ychwanegu sylw