4 milyon satırlık Python kodunu kontrol etmenin yolu. Bölüm 2

Bugün, Dropbox'ın birkaç milyon satırlık Python kodu için tür kontrolünü nasıl organize ettiğine ilişkin materyalin çevirisinin ikinci bölümünü yayınlıyoruz.

4 milyon satırlık Python kodunu kontrol etmenin yolu. Bölüm 2

Birinci bölümü okuyun

Resmi tip desteği (PEP 484)

Mypy ile ilk ciddi deneylerimizi Hack Week 2014'te Dropbox'ta gerçekleştirdik. Hack Week, Dropbox'ın ev sahipliği yaptığı bir haftalık bir etkinliktir. Bu süre zarfında çalışanlar istedikleri üzerinde çalışabilirler! Dropbox'ın en ünlü teknoloji projelerinden bazıları bu gibi etkinliklerde başladı. Bu deney sonucunda proje henüz yaygın kullanıma hazır olmasa da mypy'nin umut verici göründüğü sonucuna vardık.

O zamanlar Python tipi ipucu sistemlerini standartlaştırma fikri havadaydı. Söylediğim gibi, Python 3.0'dan bu yana işlevler için tür açıklamaları kullanmak mümkündü, ancak bunlar tanımlanmış sözdizimi ve anlambilim olmadan yalnızca rastgele ifadelerdi. Programın yürütülmesi sırasında bu ek açıklamalar çoğunlukla göz ardı edildi. Hack Week'in ardından anlambilimi standartlaştırma çalışmalarına başladık. Bu çalışma ortaya çıkmasına neden oldu KEP 484 (Guido van Rossum, Łukasz Langa ve ben bu belge üzerinde işbirliği yaptık).

Motivasyonlarımıza iki taraftan bakılabilir. İlk olarak, tüm Python ekosisteminin tür ipuçlarını (Python'da "tür açıklamaları"nın eşdeğeri olarak kullanılan bir terim) kullanma konusunda ortak bir yaklaşım benimseyebileceğini umduk. Olası riskler göz önüne alındığında bu, birbiriyle uyumsuz birçok yaklaşımı kullanmaktan daha iyi olacaktır. İkinci olarak, Python topluluğunun birçok üyesiyle tür açıklaması mekanizmalarını açıkça tartışmak istedik. Bu arzu kısmen, geniş Python programcı kitlesinin gözünde dilin temel fikirlerinden "mürted" gibi görünmek istemeyeceğimiz gerçeğinden kaynaklanıyordu. "Ördek yazma" olarak bilinen, dinamik olarak yazılan bir dildir. Toplulukta, başlangıçta, statik yazma fikrine karşı biraz şüpheli bir tutum ortaya çıkmadan edemedi. Ancak statik yazmanın zorunlu olmayacağı netleştikten sonra (ve insanlar bunun gerçekten yararlı olduğunu anladıktan sonra) bu duygu sonunda azaldı.

Sonunda benimsenen tür ipucu sözdizimi, o sırada mypy'nin desteklediğine çok benziyordu. PEP 484, 3.5 yılında Python 2015 ile piyasaya sürüldü. Python artık dinamik olarak yazılan bir dil değildi. Bu olayı Python tarihinde önemli bir dönüm noktası olarak düşünmek hoşuma gidiyor.

Taşımanın başlangıcı

Dropbox, 2015 yılı sonunda mypy üzerinde çalışmak üzere üç kişilik bir ekip oluşturdu. Bunlar arasında Guido van Rossum, Greg Price ve David Fisher da vardı. O andan itibaren durum son derece hızlı bir şekilde gelişmeye başladı. Mypy'nin büyümesinin önündeki ilk engel performanstı. Yukarıda da belirttiğim gibi projenin ilk günlerinde mypy uygulamasını C'ye çevirmeyi düşündüm ancak bu fikir şimdilik listeden çıkarıldı. Mypy gibi araçlar için yeterince hızlı olmayan CPython yorumlayıcısını kullanarak sistemi çalıştırmak zorunda kaldık. (JIT derleyicili alternatif bir Python uygulaması olan PyPy projesi de bize yardımcı olmadı.)

Neyse ki bazı algoritmik iyileştirmeler burada yardımımıza geldi. İlk güçlü “hızlandırıcı”, artımlı kontrolün uygulanmasıydı. Bu gelişmenin ardındaki fikir basitti: eğer tüm modülün bağımlılıkları önceki mypy çalıştırmasından bu yana değişmediyse, o zaman bağımlılıklarla çalışırken önceki çalıştırma sırasında önbelleğe alınan verileri kullanabiliriz. Yalnızca değiştirilen dosyalar ve bunlara bağlı olan dosyalar üzerinde tür denetimi yapmamız gerekiyordu. Hatta Mypy biraz daha ileri gitti: Bir modülün harici arayüzü değişmediyse, mypy bu modülü içe aktaran diğer modüllerin tekrar kontrol edilmesine gerek olmadığını varsaydı.

Artımlı kontrol, büyük miktarda mevcut koda açıklama eklerken bize çok yardımcı oldu. Buradaki önemli nokta, ek açıklamalar koda yavaş yavaş eklendiğinden ve yavaş yavaş geliştirildiğinden, bu sürecin genellikle mypy'nin birçok yinelemeli çalışmasını içermesidir. Mypy'nin ilk çalışması hala çok yavaştı çünkü kontrol edilmesi gereken birçok bağımlılık vardı. Daha sonra durumu iyileştirmek için uzaktan önbellekleme mekanizması uyguladık. Mypy, yerel önbelleğin büyük olasılıkla güncelliğini yitirdiğini tespit ederse, tüm kod tabanı için geçerli önbellek anlık görüntüsünü merkezi depodan indirir. Daha sonra bu anlık görüntüyü kullanarak artımlı bir kontrol gerçekleştirir. Bu bize mypy'nin performansını artırma yolunda büyük bir adım daha attı.

Bu, Dropbox'ta tip kontrolünün hızlı ve doğal bir şekilde benimsendiği bir dönemdi. 2016 yılı sonu itibariyle, tür açıklamaları içeren yaklaşık 420000 satırlık Python kodumuz zaten vardı. Birçok kullanıcı tip kontrolü konusunda heyecanlıydı. Giderek daha fazla geliştirme ekibi Dropbox mypy'yi kullanıyordu.

O zamanlar her şey yolunda görünüyordu ama hâlâ yapacak çok işimiz vardı. Projenin sorunlu alanlarını belirlemek ve öncelikle hangi konuların çözülmesi gerektiğini anlamak amacıyla periyodik iç kullanıcı anketleri yapmaya başladık (bu uygulama bugün şirkette hala kullanılmaktadır). En önemlisi, açıkça görüldüğü gibi, iki görevdi. Birincisi, kodun daha fazla kapsanmasına ihtiyacımız vardı, ikincisi ise mypy'nin daha hızlı çalışmasına ihtiyacımız vardı. Mypy'yi hızlandırma ve bunu şirket projelerine uygulama çalışmalarımızın henüz tamamlanmaktan çok uzak olduğu kesinlikle açıktı. Biz bu iki işin öneminin bilincinde olarak bunları çözmeye başladık.

Daha fazla üretkenlik!

Artımlı kontroller mypy'yi daha hızlı hale getirdi, ancak araç hala yeterince hızlı değildi. Birçok artımlı kontrol yaklaşık bir dakika sürdü. Bunun nedeni ise konjonktürel ithalattı. Bu muhtemelen Python'da yazılmış büyük kod tabanlarıyla çalışan hiç kimseyi şaşırtmayacaktır. Her biri dolaylı olarak diğerlerini ithal eden yüzlerce modülden oluşan setlerimiz vardı. Bir içe aktarma döngüsündeki herhangi bir dosya değiştirilirse, mypy'nin bu döngüdeki tüm dosyaları ve genellikle bu döngüden modülleri içe aktaran tüm modülleri işlemesi gerekiyordu. Böyle bir döngü, Dropbox'ta pek çok soruna neden olan kötü şöhretli "bağımlılık karmaşası"ydı. Bu yapı birkaç yüz modül içerdiğinden, doğrudan veya dolaylı olarak birçok test ithal edilirken, üretim kodunda da kullanıldı.

Döngüsel bağımlılıkları "çözme" olasılığını düşündük, ancak bunu yapacak kaynaklara sahip değildik. Aşina olmadığımız çok fazla kod vardı. Sonuç olarak alternatif bir yaklaşım geliştirdik. Mypy'nin "bağımlılık karmaşası" durumunda bile hızlı bir şekilde çalışmasını sağlamaya karar verdik. Bu hedefe mypy arka plan programını kullanarak ulaştık. Bir arka plan programı, iki ilginç özelliği uygulayan bir sunucu işlemidir. İlk olarak kod tabanının tamamı hakkındaki bilgileri hafızada saklar. Bu, mypy'yi her çalıştırdığınızda, içe aktarılan binlerce bağımlılıkla ilgili önbelleğe alınmış verileri yüklemeniz gerekmediği anlamına gelir. İkinci olarak, küçük yapısal birimler düzeyinde, işlevler ve diğer varlıklar arasındaki bağımlılıkları dikkatle analiz eder. Örneğin, eğer fonksiyon foo bir işlevi çağırır baro zaman bir bağımlılık var foo itibaren bar. Bir dosya değiştiğinde, arka plan programı öncelikle izole olarak yalnızca değiştirilen dosyayı işler. Daha sonra, değişen işlev imzaları gibi, o dosyada dışarıdan görülebilen değişikliklere bakar. Arka plan programı, yalnızca değiştirilen işlevi gerçekten kullanan işlevleri iki kez kontrol etmek için içe aktarmalarla ilgili ayrıntılı bilgileri kullanır. Genellikle bu yaklaşımla çok az işlevi kontrol etmeniz gerekir.

Tüm bunları uygulamak kolay olmadı, çünkü orijinal mypy uygulaması ağırlıklı olarak tek seferde bir dosyayı işlemeye odaklanmıştı. Kodda bir şeyin değiştiği durumlarda tekrarlanan kontroller gerektiren birçok sınır durumla uğraşmak zorunda kaldık. Örneğin bu, bir sınıfa yeni bir temel sınıf atandığında meydana gelir. İstediğimizi yaptıktan sonra çoğu artımlı kontrolün yürütme süresini yalnızca birkaç saniyeye indirmeyi başardık. Bu bize büyük bir zafer gibi göründü.

Daha da fazla üretkenlik!

Yukarıda tartıştığım uzaktan önbelleğe almayla birlikte, mypy arka plan programı, bir programcının az sayıda dosyada değişiklik yaparak sık sık tür kontrolü çalıştırdığında ortaya çıkan sorunları neredeyse tamamen çözdü. Ancak en az tercih edilen kullanım durumunda sistem performansı hala optimal olmaktan uzaktı. Mypy'nin temiz bir şekilde başlatılması 15 dakikadan fazla sürebilir. Ve bu bizim mutlu olacağımızdan çok daha fazlasıydı. Programcılar yeni kod yazmaya ve mevcut koda açıklamalar eklemeye devam ettikçe durum her hafta daha da kötüleşti. Kullanıcılarımız hâlâ daha fazla performansa açlardı ama biz onlarla yarı yolda buluşmaktan mutluyduk.

Mypy ile ilgili daha önceki fikirlerden birine dönmeye karar verdik. Yani Python kodunu C koduna dönüştürmek. Cython (Python'da yazılmış kodu C koduna çevirmenizi sağlayan bir sistem) ile denemeler yapmak bize gözle görülür bir hızlanma sağlamadı, bu yüzden kendi derleyicimizi yazma fikrini yeniden canlandırmaya karar verdik. Mypy kod tabanı (Python'da yazılmış) zaten gerekli tüm tür açıklamalarını içerdiğinden, sistemi hızlandırmak için bu ek açıklamaları kullanmanın faydalı olacağını düşündük. Bu fikri test etmek için hemen bir prototip oluşturdum. Çeşitli mikro kıyaslamalarda performansta 10 kattan fazla artış gösterdi. Bizim fikrimiz Cython kullanarak Python modüllerini C modüllerine derlemek ve tür açıklamalarını çalışma zamanı tür kontrollerine dönüştürmekti (genellikle tür açıklamaları çalışma zamanında göz ardı edilir ve yalnızca tür kontrol sistemleri tarafından kullanılır). Aslında mypy uygulamasını Python'dan statik olarak yazılacak şekilde tasarlanmış, tam olarak Python'a benzeyen (ve çoğunlukla işe yarayan) bir dile çevirmeyi planladık. (Bu tür diller arası geçiş, mypy projesinin bir geleneği haline geldi. Orijinal mypy uygulaması Alore'da yazılmıştı, daha sonra Java ve Python'un sözdizimsel bir melezi vardı).

CPython eklenti API'sine odaklanmak, proje yönetimi yeteneklerini kaybetmemenin anahtarıydı. Mypy'nin ihtiyaç duyduğu bir sanal makineyi veya herhangi bir kütüphaneyi uygulamaya ihtiyacımız yoktu. Ek olarak, Python ekosisteminin tamamına ve tüm araçlara (pytest gibi) erişmeye devam edebiliriz. Bu, geliştirme sırasında yorumlanmış Python kodunu kullanmaya devam edebileceğimiz anlamına geliyordu; bu, kodun derlenmesini beklemek yerine çok hızlı bir kod değişikliği yapma ve onu test etme modeliyle çalışmaya devam etmemize olanak sağladı. Deyim yerindeyse iki sandalyede oturarak harika bir iş çıkarıyormuşuz gibi görünüyordu ve bunu sevdik.

Mypyc adını verdiğimiz derleyici (türleri analiz etmek için ön uç olarak mypy'yi kullandığından) çok başarılı bir proje olduğu ortaya çıktı. Genel olarak, önbelleğe alma olmadan sık yapılan Mypy çalıştırmalarında yaklaşık 4 kat hızlanma elde ettik. Mypyc projesinin özünü geliştirmek, Michael Sullivan, Ivan Levkivsky, Hugh Hahn ve benden oluşan küçük bir ekibin yaklaşık 4 takvim ayını aldı. Bu iş miktarı, mypy'yi örneğin C++ veya Go'da yeniden yazmak için gerekenden çok daha azdı. Ve projeyi başka bir dilde yeniden yazarken yapmak zorunda kalacağımızdan çok daha az değişiklik yapmak zorunda kaldık. Ayrıca mypyc'i diğer Dropbox programcılarının kodlarını derlemek ve hızlandırmak için kullanabileceği bir seviyeye getirebileceğimizi umuyorduk.

Bu performans seviyesine ulaşmak için bazı ilginç mühendislik çözümlerini uygulamamız gerekiyordu. Böylece derleyici hızlı, düşük seviyeli C yapılarını kullanarak birçok işlemi hızlandırabilir.Örneğin, derlenmiş bir işlev çağrısı, bir C işlev çağrısına çevrilir. Ve böyle bir çağrı, yorumlanmış bir işlevi çağırmaktan çok daha hızlıdır. Sözlük aramaları gibi bazı işlemler hala CPython'dan gelen düzenli C-API çağrılarının kullanılmasını gerektiriyordu ve bu çağrılar derlendiğinde yalnızca marjinal olarak daha hızlıydı. Yorumlamanın sistem üzerinde yarattığı ek yükü ortadan kaldırabildik ancak bu durumda performans açısından sadece küçük bir kazanç sağladı.

En yaygın "yavaş" işlemleri belirlemek için kod profili oluşturma işlemi gerçekleştirdik. Bu verilerle donanmış olarak, ya mypyc'i bu tür işlemler için daha hızlı C kodu oluşturacak şekilde ayarlamaya ya da daha hızlı işlemler kullanarak karşılık gelen Python kodunu yeniden yazmaya çalıştık (ve bazen bu veya başka bir sorun için yeterince basit bir çözümümüz yoktu) . Python kodunu yeniden yazmak, genellikle derleyicinin aynı dönüşümü otomatik olarak gerçekleştirmesinden daha kolay bir çözümdü. Uzun vadede bu dönüşümlerin çoğunu otomatikleştirmek istedik ancak o zamanlar mypy'yi minimum çabayla hızlandırmaya odaklanmıştık. Ve bu hedefe doğru ilerlerken birçok köşeyi kestik.

Devam edecek ...

Sevgili okuyucular! Mypy projesinin varlığını öğrendiğinizde onunla ilgili izlenimleriniz nelerdi?

4 milyon satırlık Python kodunu kontrol etmenin yolu. Bölüm 2
4 milyon satırlık Python kodunu kontrol etmenin yolu. Bölüm 2

Kaynak: habr.com

Yorum ekle