Bugün Dropbox'ta birkaç milyon satırlık Python kodunun tip kontrolünü nasıl organize ettiğimize dair materyalin ikinci bölümünü yayımlıyoruz.
→
Resmi tip desteği (PEP 484)
Dropbox'ta 2014 Hack Week sırasında mypy ile ilk ciddi deneylerimizi gerçekleştirdik. Hack Week, Dropbox'ın bir hafta boyunca düzenlediği bir etkinliktir. Bu süre zarfında çalışanlar istedikleri herhangi bir konuda çalışabilirler! Dropbox'ın en ünlü teknolojik projelerinden bazıları bu tür etkinliklerde başlamıştır. Bu deneyin sonucunda mypy'nin umut verici göründüğüne dair çıkarımlar yaptık, ancak bu proje henüz yaygın kullanım için hazır değildi.
O dönemde Python için tip ipucu (type hint) verme sistemlerinin standartlaştırılması fikri havada dolaşıyordu. Daha önce de belirttiğim gibi, Python 3.0 ile birlikte işlevler için tip notları kullanılabiliyordu, ancak bu yalnızca keyfi ifadelerdi, belirli bir sözdizimi ve anlamı yoktu. Program çalışırken, bu notlar çoğunlukla sadece göz ardı ediliyordu. Hack Week'ten sonra, anlamların standartlaştırılması üzerine çalışmaya başladık. Bu çalışma, üzerinde Guido van Rossum, Łukasz Langa ve benim birlikte çalıştığımız bir belgedir.
Motivasyonlarımız iki açıdan değerlendirilebilirdi. İlk olarak, tüm Python ekosisteminin tip ipuçlarını kullanma konusunda ortak bir yaklaşım benimsemesini umuyorduk (tip ipuçları — Python'da «tip notları» terimi için kullanılan karşılık). Bu, olası riskler göz önüne alındığında, bir çok birbiriyle uyumsuz yaklaşım kullanmaktan daha iyi olurdu. İkinci olarak, Python topluluğunun birçok temsilcisiyle tip notları mekanizmasını açıkça tartışmak istedik. Kısmen, bu arzunun nedeni, geniş kitleler arasında Python dilinin temel düşüncelerinden
uzaklaşanlar gibi gözükmek istemememizdi. Bu, «ördek tipi» olarak bilinen dinamik olarak tiplenmiş bir dildir. Toplumda, başlangıçta, statik tiplenme fikrine karşı birkaç şüpheci tutum oluşmaktan kaçınılmadı. Ancak bu tutum sonunda zayıfladı — statik tiplenmenin zorunlu hale gelmeyeceği belli olduktan sonra (ve insanların gerçekten faydalı olduğunu anlamasıyla) bu inanç değişti.
Gösterimlerin sonunda kabul edilen tip ipuçlarının sözdizimi, o dönemde mypy'nin desteklediği sözdizime çok benziyordu. PEP 484 belgesi, Python 3.5 ile 2015 yılında piyasaya sürüldü. Python artık sadece dinamik tiplenmeyi destekleyen bir dil değildi. Bu olayı Python tarihindeki önemli bir dönüm noktası olarak düşünmekten hoşlanıyorum.
2015-ci ilin sonunda Dropbox-da mypy üzərində işləmək üçün üç nəfərdən ibarət bir komanda yaradıldı. Bu komandaya Guido van Rossum, Greg Price və David Fisher daxil idi. O andan etibarən vəziyyət sürətlə inkişaf etməyə başladı. Mypy-nin böyüməsi yolunda ilk maneə performans oldu. Yuxarıda da qeyd etdiyim kimi, layihənin başlanğıc dövründə mypy-nin reallaşmasını C dilinə keçirmək barədə düşünürdüm, amma bu fikir hələlik siyahıdan çıxarıldı. Biz mypy üçün CPython interpretatorunun istifadə olunması ilə əlaqəli qaldıq, bu da mypy kimi alətlər üçün kifayət qədər sürətli deyil. (PyPy layihəsi, JIT tərtibçisi ilə alternativ Python reallaşması, bizə də kömək etmədi.)
Xoşbəxtlikdən, burada bəzi alqoritmik təkmilləşdirmələr köməyimizə çatdı. İlk güclü "sürətləndirici" inkremental yoxlamanın reallaşması oldu. Bu təkmilləşdirmənin ideyası sadə idi: əgər modulumuzun bütün asılılıqları əvvəlki mypy işindən bəri dəyişməyibsə, o zaman asılılıqlar üzərində işlədiyimiz zaman, əvvəllki iş sessiyasında kəsilmiş məlumatları istifadə edə bilərik. Yalnız dəyişdirilmiş fayllardakı tip yoxlamalarını həyata keçirməliydik və onlara asılı olan fayllarda. Mypy daha da irəliləyərək: əgər modulumuzun xarici interfeysi dəyişməyibsə – mypy bu modulu idxal edən digər modulları yenidən yoxlamaq lazım olmadığını hesab edirdi.
İncremental yoxlama, mövcud kodun böyük həcmli annotasiyasını həyata keçirmək üçün bizə çox kömək etdi. Məsələ burasındadır ki, bu proses adətən mypy-ni bir çox iterativ işlərlə əhatə edir, çünki annotasiyalar kodu tədricən əlavə edilir və tədricən yaxşılaşdırılır. İlk mypy işinin hələ də çox yavaş olması, onun yerinə yetirilməsi zamanı bir çox asılılığın yoxlanmasını tələb etməsidir. Bu vəziyyəti yaxşılaşdırmaq üçün biz uzaqdan kəşf edici mexanizmini həyata keçirdik. Əgər mypy yerli kəşfi köhnəlmiş hesab edirsə, o, bütün kod bazası üçün mərkəzləşdirilmiş repozitoriyadan kəşf görünüşünü yükləyir. Sonra bu görünüşdən istifadə edərək inkremental yoxlama həyata keçirir. Bu, mypy-nin performansını artırmaq yolunda bizi bir addım irəlilədib.
Bu, Dropbox-da tip yoxlama sisteminin sürətli və təbii şəkildə tətbiq olunduğu bir dövr idi. 2016-cı ilin sonunda, bizdə artıq təxminən 420000 Python kod sətiri var idi ki, bunlar da tip annotasiyaları ilə təmin edilmişdi. Bir çox istifadəçi tip yoxlamasına böyük həvəs göstərdi. Dropbox-da mypy istifadə edən inkişaf komandalarının sayı artdı.
Hər şey o vaxt yaxşı görünürdü, amma hələ çox işimiz qalmışdı. Problemli sahələri müəyyən etmək və hansı sualların ilk növbədə həll edilməli olduğunu anlamaq üçün mütəmadi istifadəçi sorğuları keçirməyə başladıq (bu praktika şirkətdə indiyə qədər istifadə olunur). Aydın oldu ki, ən vacib iki problem var idi. Birincisi, kodun daha çox tip örtüyünə ehtiyac var idi, ikincisi isə mypy-nın daha sürətli işləməsi lazım idi. mypy-nı sürətləndirmək və onu şirkətin layihələrinə yerləşdirmək istiqamətindəki işimizin hələ də tamamlanmadığı tamamilə aydın idi. Bu iki problemin əhəmiyyətini tamamlaya bildiyimizdən, onların həllinə başladıq.
Daha çox performans!
İncremental yoxlamalar mypy-ni sürətləndirdi, amma bu alət hələ də kifayət qədər sürətli deyil. Çox sayda ikincremental yoxlama təxminən bir dəqiqə sürdü. Bunun səbəbi ilkin dövriliyidir. Bu, Python-da yazılmış böyük kod bazaları ilə işləməyi təcrübəsi olanları təəccübləndirmir. Bizdə yüzlərlə moduldan ibarət dəstlər var idi ki, hər biri digər bütün modulları dolayı yolla idxal edirdi. İxrac edilən hər hansı bir fayl dövrü importlarla dəyişdirsə, mypy bu dövrə daxil olan bütün faylları işlətməli olurdu, tez-tez isə bu dövrədəki modulları idxal edən digər modulları da. Belə bir dövr, Dropbox-da bir çox narahatlığa səbəb olan məşhur
Çevrili asılılıqları "çözümləməyi" düşündük, amma bunu etmək üçün resurslarımız yox idi. Tanış olmadığımız çox kod var idi. Nəticədə alternativ bir yanaşmaya keçdik. Mypy-nın "asılılıq topaları" olan hallarda belə sürətlə işləməsini təmin etməyə qərar verdik. Bu məqsədə mypy demonunu tətbiq edərək nail olduq. Demon, iki maraqlı funksiyanı icra edən server prosesi. Birincisi, o, bütün kod bazası haqqında məlumatı yadda saxlayır. Bu, hər mypy işə salındıqda, minlərlə idxal olunan asılılıqlara aid gözlənilən məlumatları yükləməməyi təmin edir. İkincisi, o, funksiyalar və digər varlıqlar arasında asılılıqları diqqətlə, incə struktural səviyyəsində analiz edir. Məsələn, əgər funksiya foo funksiyanı çağırırsa bar", o zaman asılılıq var. foo aylıq bar. Fayl dəyişəndə — demon əvvəlcə, izolyasiyada, yalnız dəyişən faylı emal edir. Sonra o, bu faylın xaricdən görünən dəyişikliklərinə, dəyişən funksiya imzalarına baxır. Demon, yalnız dəyişdirilmiş funksiyanı həqiqətən istifadə edən funksiyaları yoxlamaq üçün idxallara dair ətraflı məlumatdan istifadə edir. Bu yanaşma ilə adətən yalnız bir neçə funksiyanı yoxlamaq lazım gəlir.
Bütün bunların həyata keçirilməsi asan olmadı, çünki mypy-nin ilkin versiyası bir vaxtda bir fayl üzərində işləməyə çox meyl edirdi. Biz çoxsaylı hədd vəziyyətləri ilə üzləşməli olduq ki, bunlar kodda bir şey dəyişəndə yenidən yoxlanılma tələb edirdi. Məsələn, bu, bir sinfə yeni bir əsas sinif təyin edildikdə baş verir. İstədiyimiz işi bitirdikdən sonra, əksər inkremental yoxlamaların icra müddətini bir neçə saniyəyə endirməyə müvəffəq olduq. Bu, bizim üçün böyük bir qələbə idi.
Daha çox məhsuldarlıq!
Yuxarıda danışdığım uzaqdan keşləmə ilə birlikdə, mypy demonu, proqramçının tez-tez növ yoxlamasını kiçik fayllarda dəyişiklik edərkən başladığı zaman yaranan problemləri demək olar ki, tamamilə həll etdi. Lakin sistemin yoxsul variantda istifadəsi zamanı məhsuldarlığı hələ də optimaldan çox uzaq idi. mypy-nin tam başlayışı 15 dəqiqədən çox apara bilərdi. Bu, bizi qane edəcəkdən çox idi. H hər həftə vəziyyət daha da pisləşirdi, çünki proqramçılar yeni kod yazmağa və mövcud koda annotasiyalar əlavə etməyə davam edirdilər. İstifadəçilərimiz daha çox məhsuldarlıq tələb edirdilər, biz də məmnuniyyətlə onlara kömək etməyə hazır idik.
Biz mypy ilə bağlı əvvəlki fikirlərdən birinə qayıtmağı qərara aldıq. Yəni, Python kodunu C koduna çevirmək. Cython ilə apardığımız eksperimentlər (bu, Python-da yazılmış kodu C koduna çevirməyə imkan verən bir sistemdir) bizə görünən bir sürət artımı vermədi, buna görə də öz kompilatorumuzu yaratmağı yenidən düşünməyə başladıq. Mypy-nin kod bazası (Python-da yazılmış) artıq bütün lazım olan tip annotasiyalarını özündə birləşdirirdi, buna görə də bu annotasiyalardan istifadə edərək sistemin işini sürətləndirmək üçün bir cəhd etməyə dəyəcəyini düşündük. Bu fikri yoxlamaq üçün tez bir prototip yaratdım. Bu prototip müxtəlif mikro-benchmarklarda 10 qat daha yüksək performans göstərdi. Bizim fikrimiz Python modullarını C moduluna Cython vasitəsilə kompilyasiya etmək və tip annotasiyalarını proqramın icrası zamanı həyata keçiriləcək tip yoxlamalarına çevirmək idi (tip annotasiyaları adətən proqram icrası zamanı nəzərə alınmır və yalnız tip yoxlama sistemləri tərəfindən istifadə olunur). Əslində, mypy-nin implementasiyasını Python-dan statik tipli bir dilə köçürməyi planlaşdırırdıq ki, bu da Python-un özünə bənzəyəcək (və böyük ölçüdə çalışacaq). (Bu cür dilarası köçürmə mypy layihəsi üçün bir ənənəyə çevrilmişdir. Mypy-nin ilkin implementasiyası Alore-da yazılmışdır, sonra isə Java və Python-un sintaksis hibridi meydana gəlmişdir.)
CPython genişlənmələrinin API-yə yönəlmək layihə idarəetmə imkanlarını itirməmək üçün açar idi. Mypy-yə lazım olan virtual maşın və ya hər hansı kitabxanaları həyata keçirmək lazım deyildi. Üstəgəl, Python ekosisteminin bütün imkanları bizə hələ də təqdim olunacaq, bütün alətlər (məsələn, pytest) əlçatan olacaqdı. Bu, inkişaf zamanı interpretasiya olunmuş Python kodundan istifadə etməyə davam etməyimiz demək idi, bu da kodu kompilyasiya etməyə gözləməzdən çox, kodda düzəlişlər etmə və test etmə sürətli sxemini davam etdirməyə imkan verirdi. Elə görünürdü ki, biz iki stulda bir yerə oturmaqda mükəmməl müvəffəq oluruq və bundan zövq alırdıq.
Mypy adlı ön yüzü tip analizi için kullanan mypyc adını verdiğimiz derleyici oldukça başarılı bir proje oldu. Genel olarak, önbellekleme olmadan mypy'nin sık başlatmalarında yaklaşık dört kat hız artışı sağladık. Mypy projesinin çekirdek geliştirmesi, Michael Sullivan, Ivan Levkiwski, Hugh Han ve benim yer aldığım küçük bir ekip tarafından yaklaşık dört takvim ayı sürdü. Bu iş yükü, mypy'yi örneğin C++ veya Go'ya yeniden yazmak için gerekenlerden çok daha az ölçekliydi. Ayrıca, başka bir dilde yeniden yazma durumunda yapmamız gereken değişikliklerden çok daha azını projeye eklemek zorunda kaldık. Diğer yandan, mypyc'yi Dropbox'taki diğer geliştiricilerin kodlarını derleyip hızlandırması için kullanabileceği bir seviyeye getirmeyi umuyorduk.
Bu seviyede bir performans elde etmek için bazı ilginç mühendislik çözümleri uygulamak zorunda kaldık. Derleyici, hızlı düşük seviyeli C yapılarının kullanımı sayesinde birçok işlemi hızlandırabiliyor. Örneğin, derlenmiş bir fonksiyon çağrısı, C fonksiyonu çağrısına dönüştürülüyor. Bu tür bir çağrı, yorumlanan bir fonksiyon çağrısından çok daha hızlı bir şekilde gerçekleştiriliyor. Sözlüklerdeki arama gibi bazı işlemler, Cpython'dan normal C-API çağrıları kullanmaya dayanıyordu ve derleme sonrası sadece biraz daha hızlıydı. Yorumlamanın yarattığı ek yükten kurtulmayı başardık, ancak bu durumda yalnızca küçük bir performans kazanımı sağladı.
En yaygın 'yavaş' işlemleri belirlemek için kodu profilledik. Elde ettiğimiz verilerle, ya mypyc'yi bu işlemler için daha hızlı C kodu üretecek şekilde 'ayar yaptık', ya da ilgili Python kodunu daha hızlı işlemlerle yeniden yazmaya çalıştık (bazen basit bir çözüm bulmakta yeterli olamıyorduk). Python kodunu yeniden yazmak, derleyicide aynı dönüşümü otomatik olarak gerçekleştirmekten genellikle daha kolay bir çözüm oluyordu. Uzun vadede, bu dönüşümlerin pek çoğunu otomatikleştirmek istiyorduk, ancak o anda mypy'yi en az çabayla hızlandırmaya odaklanmıştık. Bu hedefe ulaşırken birkaç köşeden kısaldık.
Davamı gəlir…
Hörmətli oxucular! Mypy projesini öğrendiğinizde nasıl bir izlenim bıraktı?
Mənbə: habr.com
