4 milyon sətirlik Python kodunu yazmağa gedən yol. 2-ci hissə

Bu gün biz Dropbox-un bir neçə milyon sətir Python kodunun tip nəzarətini necə təşkil etdiyinə dair materialın tərcüməsinin ikinci hissəsini dərc edirik.

4 milyon sətirlik Python kodunu yazmağa gedən yol. 2-ci hissə

Birinci hissəni oxuyun

Rəsmi dəstək növü (PEP 484)

2014-cü il Hack Week zamanı Dropbox-da mypy ilə ilk ciddi təcrübələrimizi etdik. Hack Week Dropbox tərəfindən təşkil edilən bir həftəlik tədbirdir. Bu müddət ərzində işçilər hər şey üzərində işləyə bilərlər! Dropbox-un ən məşhur texnoloji layihələrindən bəziləri bu kimi tədbirlərdə başladı. Bu təcrübə nəticəsində biz mypy-nin perspektivli göründüyü qənaətinə gəldik, baxmayaraq ki, bu layihə hələ geniş istifadəyə hazır deyil.

O dövrdə Python tipli işarə sistemlərinin standartlaşdırılması ideyası havada idi. Dediyim kimi, Python 3.0-dan bəri funksiyalarda tip annotasiyalarından istifadə etmək mümkün idi, lakin bunlar müəyyən edilmiş sintaksis və ya semantika olmadan sadəcə ixtiyari ifadələr idi. Proqramın icrası zamanı bu annotasiyalar, əksər hallarda, sadəcə olaraq nəzərə alınmadı. Hack Week-dən sonra semantik standartlaşdırma üzərində işləməyə başladıq. Bu iş ortaya çıxmasına səbəb olmuşdur PEP 484 (Guido van Rossum, Lukasz Lanqa və mən bu sənəd üzərində birlikdə işlədik).

Bizim motivlərimizə iki tərəfdən baxmaq olardı. Birincisi, biz ümid edirdik ki, bütün Python ekosistemi tip göstərişlərindən istifadə etmək üçün ümumi yanaşma qəbul edə bilər (tip göstərişləri Python-da “tip annotasiyalarının” analoqu kimi istifadə olunan termindir). Bu, mümkün riskləri nəzərə alsaq, bir çox qarşılıqlı uyğunsuz yanaşmalardan istifadə etməkdən daha yaxşı olardı. İkincisi, biz Python cəmiyyətində bir çox insanla tip annotasiyasının mexanikasını açıq şəkildə müzakirə etmək istədik. Qismən, bu istək, Python proqramçılarının geniş ictimaiyyətinin gözündə dilin əsas ideyalarından "mürtəd" kimi görünmək istəməməyimizlə diktə edildi. Bu, "ördək yazma" ilə tanınan dinamik şəkildə yazılmış bir dildir. İcmada, lap başlanğıcda, statik yazma ideyasına bir qədər şübhəli münasibət kömək edə bilməzdi. Lakin statik yazmağın məcburi olmayacağı aydınlaşdıqdan sonra (və insanlar bunun həqiqətən faydalı olduğunu başa düşdükdən sonra) bu əhval-ruhiyyə nəhayət yox oldu.

Nəhayət qəbul edilən tip işarəsi sintaksisi o zaman mypy tərəfindən dəstəklənənə çox oxşar idi. PEP 484 3.5-ci ildə Python 2015 ilə buraxıldı. Python artıq dinamik tipli bir dil deyildi. Mən bu hadisəni Python tarixində bir mərhələ kimi düşünməyi xoşlayıram.

Miqrasiyanın başlanğıcı

2015-ci ilin sonunda Dropbox-da mypy üzərində işləmək üçün üç nəfərdən ibarət komanda yaradıldı. Buraya Guido van Rossum, Greg Price və David Fisher daxildir. Həmin andan vəziyyət son dərəcə sürətlə inkişaf etməyə başladı. Mypy artımına ilk maneə performans idi. Yuxarıda eyham vurduğum kimi, layihənin ilk günlərində mypy tətbiqini C dilinə çevirməyi düşünürdüm, lakin bu fikir hələlik siyahıdan çıxarılıb. Sistemin işə salınması üçün CPython tərcüməçisindən istifadə olunduğundan asılıyıq, bu, mypy kimi alətlər üçün kifayət qədər sürətli deyil. (JIT kompilyatoru ilə Python-un alternativ tətbiqi olan PyPy layihəsi də kömək etmədi.)

Xoşbəxtlikdən, burada bəzi alqoritmik təkmilləşdirmələr köməyimizə gəldi. İlk güclü "sürətləndirici" artımlı yoxlamaların həyata keçirilməsi idi. Bu təkmilləşdirmənin ideyası sadə idi: əgər modulun bütün asılılıqları mypy-nin əvvəlki işə salınmasından sonra dəyişməyibsə, onda biz asılılıqlarla işləmək üçün əvvəlki sessiya zamanı keşlənmiş məlumatlardan istifadə edə bilərik. Bizə yalnız dəyişdirilmiş fayllarda və onlardan asılı olan fayllarda tip yoxlanmasını həyata keçirməli olduq. Mypy hətta bir qədər irəli getdi: əgər modulun xarici interfeysi dəyişməyibsə, mypy hesab edirdi ki, bu modulu idxal edən digər modulları yenidən yoxlamağa ehtiyac yoxdur.

Mövcud koda böyük həcmdə şərh yazarkən artımlı yoxlama bizə çox kömək etdi. Məsələ burasındadır ki, bu proses adətən mypy-nin çoxlu iterativ dövrlərini əhatə edir, çünki annotasiyalar koda tədricən əlavə edilir və tədricən təkmilləşdirilir. Mypy-nin ilk işə salınması hələ də çox yavaş idi, çünki çoxlu asılılıqların yoxlanılmasını tələb edirdi. Sonra vəziyyəti yaxşılaşdırmaq üçün uzaqdan keşləmə mexanizmini tətbiq etdik. mypy yerli keşin çox güman ki, köhnəldiyini aşkar edərsə, o, mərkəzləşdirilmiş depodan bütün kod bazası üçün cari keş snapşotunu yükləyir. Sonra həmin görüntüdən istifadə edərək artımlı yoxlama aparır. Bu, mypy-nin performans təkmilləşdirmə səyahətində irəliyə doğru böyük bir addımdır.

Bu, Dropbox-un tip yoxlama sisteminin sürətli və təbii qəbulu dövrü idi. 2016-cı ilin sonuna qədər bizdə artıq tip annotasiyaları ilə təxminən 420000 sətir Python kodu var. Bir çox istifadəçi tip yoxlanışına həvəsli olub. Daha çox inkişaf qrupu Dropbox mypy istifadə etdi.

O zaman hər şey yaxşı görünürdü, amma hələ çox işimiz var idi. Layihənin problemli sahələrini müəyyən etmək və ilk növbədə hansı məsələlərin həll edilməli olduğunu başa düşmək üçün istifadəçilər arasında vaxtaşırı daxili sorğular keçirməyə başladıq (bu təcrübə şirkətdə bu gün də istifadə olunur). Ən vacibi, aydın olduğu kimi, iki vəzifə idi. Birincisi, növlərlə daha çox kod əhatəsinə ehtiyacımız idi, ikincisi, mypy-nin daha sürətli işləməsi lazım idi. Çox aydın idi ki, mypy-nin sürətləndirilməsi və şirkətin layihələrinə daxil edilməsi üzrə işlərimiz hələ bitməmişdir. Biz bu iki vəzifənin əhəmiyyətini tam dərk edərək, onların həllini tapmağa başladıq.

Daha çox performans!

Artan yoxlamalar mypy-ni daha sürətli etdi, lakin hələ də kifayət qədər sürətli deyildi. Bir çox artımlı yoxlamalar təxminən bir dəqiqə davam etdi. Buna səbəb dövri idxal olub. Bu, yəqin ki, Python-da yazılmış böyük kod bazaları ilə işləyən kimsəni təəccübləndirməyəcək. Yüzlərlə modul dəstimiz var idi, onların hər biri digərlərini dolayı yolla idxal edirdi. Əgər idxal döngəsindəki hər hansı bir fayl dəyişərsə, mypy həmin döngədəki bütün faylları və çox vaxt modulları həmin döngədən idxal edən modulları emal etməli idi. Belə dövrələrdən biri də Dropbox-da çoxlu problemlərə səbəb olan məşhur “asılılıq dolaşıqlığı” idi. Bir dəfə bu struktur bir neçə yüz moduldan ibarət idi, birbaşa və ya dolayı yolla idxal edilərkən, bir çox testlər istehsal kodunda da istifadə edildi.

Biz dairəvi asılılıqları aradan qaldırmağı düşündük, lakin bunu etmək üçün resurslarımız yox idi. Bizə tanış olmayan həddən artıq kod var idi. Nəticə etibarı ilə biz alternativ bir yanaşma tapdıq. Biz mypy-ni hətta "asılılıqların dolaşıqlığı" ilə də tez işləməyə qərar verdik. Bu məqsədə mypy demonu ilə nail olduq. Demon iki maraqlı funksiyanı həyata keçirən server prosesidir. Birincisi, bütün kod bazası haqqında məlumatı yaddaşda saxlayır. Bu o deməkdir ki, hər dəfə mypy-ni işə saldığınız zaman minlərlə idxal edilmiş asılılıqlarla bağlı keşlənmiş məlumatları yükləməli olmayacaqsınız. İkincisi, o, kiçik struktur bölmələr səviyyəsində funksiyalar və digər qurumlar arasındakı asılılıqları diqqətlə təhlil edir. Məsələn, əgər funksiya foo funksiyanı çağırır bar, onda asılılıq yaranır foo etibarən bar. Fayl dəyişdikdə, demon əvvəlcə ayrı-ayrılıqda yalnız dəyişdirilmiş faylı emal edir. Sonra o, dəyişdirilmiş funksiya imzaları kimi həmin faylda xaricdən görünən dəyişiklikləri axtarır. Daemon idxal haqqında ətraflı məlumatdan yalnız dəyişdirilmiş funksiyadan istifadə edən funksiyaları iki dəfə yoxlamaq üçün istifadə edir. Adətən, bu yanaşma ilə yoxlamaq üçün çox az funksiya var.

Bütün bunları həyata keçirmək çətin idi, çünki mypy-nin ilkin tətbiqi hər dəfə bir faylın işlənməsinə çox diqqət yetirirdi. Kodda nəyinsə dəyişdiyi hallarda təkrar yoxlama tələb edən bir çox kənar vəziyyətlərlə qarşılaşmalı olduq. Məsələn, bu, sinfə yeni əsas sinif təyin edildikdə baş verir. İstədiyimizi etdikdən sonra biz artımlı yoxlamaların əksəriyyətinin icra müddətini bir neçə saniyəyə qədər azalda bildik. Bu, bizə böyük qələbə kimi görünürdü.

Daha çox performans!

Yuxarıda bəhs etdiyim uzaqdan keşləmə ilə birlikdə mypy demonu proqramçı tez-tez tip yoxlanışı həyata keçirdikdə, az sayda faylda dəyişiklik etdikdə yaranan problemləri demək olar ki, tamamilə həll etdi. Bununla belə, sistemin ən pis istifadə vəziyyətində performansı hələ də optimaldan uzaq idi. Mypy-nin təmiz başlanğıcı 15 dəqiqədən çox çəkə bilər. Və bu, bizim istədiyimizdən çox idi. Proqramçılar yeni kod yazmağa və mövcud koda annotasiyalar əlavə etməyə davam etdikcə hər həftə işlər daha da pisləşirdi. İstifadəçilərimiz hələ də daha çox performansa can atırdılar, lakin biz onlarla tanış olmaqdan məmnun olduq.

Biz ilk mypy ideyalarından birinə qayıtmaq qərarına gəldik. Yəni, Python kodunu C koduna çevirmək üçün. Cython ilə təcrübələr (bu, Python-da yazılmış kodu C koduna çevirməyə imkan verən sistemdir) bizə heç bir görünən sürət vermədi, ona görə də öz kompilyatorumuzu yazmaq ideyasını canlandırmaq qərarına gəldik. Mypy kod bazası (Python-da yazılmış) artıq bütün lazımi tipli annotasiyaları ehtiva etdiyinə görə, sistemi sürətləndirmək üçün bu annotasiyalardan istifadə etməyə cəhd etməyə dəyər görünürdü. Bu fikri sınamaq üçün tez bir prototip yaratdım. Bu, müxtəlif mikro etalonlarda performansda 10 dəfədən çox artım göstərdi. İdeyamız Python modullarını Cython istifadə edərək C modullarına tərtib etmək və tip annotasiyalarını iş vaxtı tip yoxlanışlarına çevirmək idi (adətən tip annotasiyaları icra zamanı nəzərə alınmır və yalnız tip yoxlayanlar tərəfindən istifadə olunur). ). Biz, əslində, mypy-nin tətbiqini Python-dan statik olaraq yazılmış, tam olaraq Python kimi görünəcək (və əksər hallarda işləyəcək) dilə tərcümə etməyi planlaşdırırdıq. (Bu cür dillərarası miqrasiya mypy layihəsi ənənəsinə çevrilib. Mypy-nin orijinal tətbiqi Alore-də yazılmışdı, sonra Java və Python-un sintaktik hibridləri var idi).

CPython genişləndirmələri API-yə diqqət yetirmək layihənin idarə edilməsi imkanlarını itirməmək üçün əsas idi. Bizə virtual maşın və ya mypy üçün lazım olan hər hansı bir kitabxana tətbiq etməli deyildik. Bundan əlavə, bütün Python ekosistemi hələ də bizim üçün əlçatan olacaq, bütün alətlər (məsələn, pytest) mövcud olacaq. Bu o demək idi ki, biz inkişaf zamanı şərh edilmiş Python kodundan istifadə etməyə davam edə bilərik ki, bu da bizə kodun yığılmasını gözləmək əvəzinə, koda dəyişikliklər etmək və onu sınaqdan keçirmək üçün çox sürətli sxemdən istifadə edərək işləməyə davam etməyə imkan verəcəkdi. Deyəsən, iki stulda oturub, belə demək mümkünsə, əla iş görürük və xoşumuza gəldi.

Mypyc adını verdiyimiz kompilyator (çünki o, mypy-dən tipləri təhlil etmək üçün front-end kimi istifadə edir) çox uğurlu bir layihə oldu. Ümumilikdə - biz keşləmə istifadə etmədən tez-tez mypy işə salınmalarının təxminən 4x sürətlənməsinə nail olduq. Təxminən 4 təqvim ayı ərzində mypyc layihəsinin əsasını hazırlamaq üçün Michael Sullivan, İvan Levkivski, Hugh Khan və məndən ibarət kiçik bir komanda lazım idi. Bu iş miqdarı, məsələn, C ++ və ya Go-da mypy-ni yenidən yazmaq üçün lazım olandan daha az iddialı idi. Və biz layihəni başqa dildə yenidən yazarkən etməli olduğumuzdan daha az dəyişiklik etməli olduq. Biz həmçinin mypyc-i elə səviyyəyə çatdıra biləcəyimizə ümid edirdik ki, Dropbox-dan olan digər proqramçılar ondan kodlarını tərtib etmək və sürətləndirmək üçün istifadə edə bilsinlər.

Bu performans səviyyəsinə nail olmaq üçün bəzi maraqlı mühəndis həllərini tətbiq etməli olduq. Məsələn, kompilyator sürətli, aşağı səviyyəli C konstruksiyalarından istifadə etməklə bir çox əməliyyatları sürətləndirə bilər.Məsələn, tərtib edilmiş funksiyaya edilən çağırış C funksiyasına edilən çağırışa çevrilir. Və belə bir çağırış şərh edilmiş funksiyanı çağırmaqdan daha sürətlidir. Lüğət axtarışları kimi bəzi əməliyyatlar hələ də adi CPython C-API zənglərindən istifadə etməklə məhdudlaşırdı ki, bunlar tərtib edildikdən sonra bir qədər daha sürətli idi. Biz şərhin yaratdığı sistemdəki əlavə yükdən qurtula bildik, lakin bu, bu vəziyyətdə yalnız kiçik bir performans qazancı verdi.

Ən ümumi "yavaş" əməliyyatları müəyyən etmək üçün biz kod profilini həyata keçirdik. Bu məlumatlarla silahlanaraq, biz ya mypyc-i belə əməliyyatlar üçün daha sürətli C kodu yaratmaq üçün düzəltməyə çalışdıq və ya daha sürətli əməliyyatlardan istifadə edərək müvafiq Python kodunu yenidən yazmağa çalışdıq (və bəzən bu və ya digər problem üçün kifayət qədər sadə həllimiz yox idi). . Python kodunu yenidən yazmaq çox vaxt kompilyatorun eyni transformasiyanı avtomatik etməsindən daha asan bir problemin həlli olduğunu sübut etdi. Uzunmüddətli perspektivdə biz bu transformasiyaların çoxunu avtomatlaşdırmaq istəyirdik, lakin o zaman biz mümkün qədər az səylə mypy-ni sürətləndirməyə diqqət yetirmişdik. Biz isə bu məqsədə doğru irəliləyərək bir neçə küncü kəsdik.

Davam etmək üçün ...

Hörmətli oxucular! mypy layihəsi haqqında öyrəndiyiniz zaman təəssüratlarınız necə idi?

4 milyon sətirlik Python kodunu yazmağa gedən yol. 2-ci hissə
4 milyon sətirlik Python kodunu yazmağa gedən yol. 2-ci hissə

Mənbə: www.habr.com

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