Laluan untuk menyemak taip 4 juta baris kod Python. Bahagian 2

Hari ini kami menerbitkan bahagian kedua terjemahan bahan tentang cara Dropbox mengatur kawalan jenis untuk beberapa juta baris kod Python.

Laluan untuk menyemak taip 4 juta baris kod Python. Bahagian 2

β†’ Baca bahagian satu

Sokongan jenis rasmi (PEP 484)

Kami menjalankan percubaan serius pertama kami dengan mypy di Dropbox semasa Minggu Hack 2014. Minggu Hack ialah acara seminggu yang dihoskan oleh Dropbox. Pada masa ini, pekerja boleh bekerja pada apa sahaja yang mereka mahu! Beberapa projek teknologi Dropbox yang paling terkenal bermula pada acara seperti ini. Hasil daripada percubaan ini, kami membuat kesimpulan bahawa mypy kelihatan menjanjikan, walaupun projek itu belum lagi sedia untuk digunakan secara meluas.

Pada masa itu, idea untuk menyeragamkan sistem pembayang jenis Python telah muncul. Seperti yang saya katakan, sejak Python 3.0 adalah mungkin untuk menggunakan anotasi jenis untuk fungsi, tetapi ini hanyalah ungkapan sewenang-wenangnya, tanpa sintaks dan semantik yang ditentukan. Semasa pelaksanaan program, anotasi ini, sebahagian besarnya, hanya diabaikan. Selepas Minggu Hack, kami mula mengusahakan penyeragaman semantik. Kerja ini membawa kepada kemunculan PEP 484 (Guido van Rossum, Łukasz Langa dan saya bekerjasama dalam dokumen ini).

Motif kita boleh dilihat dari dua sisi. Pertama, kami berharap keseluruhan ekosistem Python boleh menggunakan pendekatan biasa untuk menggunakan petunjuk jenis (istilah yang digunakan dalam Python sebagai setara dengan "anotasi jenis"). Ini, memandangkan kemungkinan risiko, adalah lebih baik daripada menggunakan banyak pendekatan yang saling tidak serasi. Kedua, kami ingin membincangkan secara terbuka mekanisme anotasi jenis dengan ramai ahli komuniti Python. Keinginan ini sebahagiannya ditentukan oleh fakta bahawa kami tidak mahu kelihatan seperti "murtad" daripada idea asas bahasa itu di mata ramai pengaturcara Python. Ia ialah bahasa yang ditaip secara dinamik, yang dikenali sebagai "penaipan itik". Dalam komuniti, pada awalnya, sikap yang agak mencurigakan terhadap idea menaip statik tidak dapat membantu tetapi timbul. Tetapi sentimen itu akhirnya berkurangan selepas menjadi jelas bahawa menaip statik tidak akan menjadi wajib (dan selepas orang menyedari bahawa ia sebenarnya berguna).

Sintaks pembayang jenis yang akhirnya diterima pakai sangat serupa dengan apa yang disokong oleh mypy pada masa itu. PEP 484 dikeluarkan dengan Python 3.5 pada tahun 2015. Python bukan lagi bahasa yang ditaip secara dinamik. Saya suka menganggap peristiwa ini sebagai peristiwa penting dalam sejarah Python.

Permulaan penghijrahan

Pada penghujung tahun 2015, Dropbox mencipta satu pasukan yang terdiri daripada tiga orang untuk mengusahakan mypy. Mereka termasuk Guido van Rossum, Greg Price dan David Fisher. Sejak saat itu, keadaan mula berkembang dengan sangat cepat. Halangan pertama kepada pertumbuhan mypy adalah prestasi. Seperti yang saya bayangkan di atas, pada hari-hari awal projek saya berfikir tentang menterjemahkan pelaksanaan mypy ke dalam C, tetapi idea ini telah dipalang daripada senarai buat masa ini. Kami terjebak dengan menjalankan sistem menggunakan penterjemah CPython, yang tidak cukup pantas untuk alatan seperti mypy. (Projek PyPy, pelaksanaan Python alternatif dengan pengkompil JIT, juga tidak membantu kami.)

Nasib baik, beberapa penambahbaikan algoritma telah membantu kami di sini. "Pemecut" berkuasa pertama ialah pelaksanaan pemeriksaan tambahan. Idea di sebalik penambahbaikan ini adalah mudah: jika semua kebergantungan modul tidak berubah sejak menjalankan mypy sebelumnya, maka kita boleh menggunakan data yang dicache semasa menjalankan sebelumnya sambil bekerja dengan kebergantungan. Kami hanya perlu melakukan pemeriksaan jenis pada fail yang diubah suai dan pada fail yang bergantung padanya. Mypy malah pergi lebih jauh: jika antara muka luaran modul tidak berubah, mypy mengandaikan bahawa modul lain yang mengimport modul ini tidak perlu disemak semula.

Pemeriksaan tambahan telah banyak membantu kami apabila menganotasi sejumlah besar kod sedia ada. Intinya ialah proses ini biasanya melibatkan banyak larian mypy berulang kerana anotasi ditambahkan secara beransur-ansur pada kod dan dipertingkatkan secara beransur-ansur. Larian pertama mypy masih sangat perlahan kerana ia mempunyai banyak kebergantungan untuk diperiksa. Kemudian, untuk memperbaiki keadaan, kami melaksanakan mekanisme caching jauh. Jika mypy mengesan bahawa cache setempat berkemungkinan sudah lapuk, ia memuat turun petikan cache semasa untuk keseluruhan pangkalan kod daripada repositori berpusat. Ia kemudian melakukan semakan tambahan menggunakan syot kilat ini. Ini telah membawa kami satu lagi langkah besar ke arah meningkatkan prestasi mypy.

Ini adalah tempoh penggunaan pantas dan semula jadi bagi pemeriksaan jenis di Dropbox. Menjelang akhir tahun 2016, kami sudah mempunyai kira-kira 420000 baris kod Python dengan anotasi jenis. Ramai pengguna bersemangat tentang pemeriksaan jenis. Semakin banyak pasukan pembangunan menggunakan Dropbox mypy.

Segala-galanya kelihatan baik ketika itu, tetapi kami masih banyak yang perlu dilakukan. Kami mula menjalankan tinjauan pengguna dalaman secara berkala untuk mengenal pasti kawasan masalah projek dan memahami isu yang perlu diselesaikan terlebih dahulu (amalan ini masih digunakan dalam syarikat hari ini). Yang paling penting, kerana ia menjadi jelas, adalah dua tugas. Pertama, kami memerlukan lebih banyak liputan jenis kod, kedua, kami memerlukan mypy untuk berfungsi dengan lebih pantas. Jelas sekali bahawa kerja kami untuk mempercepatkan mypy dan melaksanakannya ke dalam projek syarikat masih jauh dari selesai. Kami, menyedari sepenuhnya kepentingan kedua-dua tugas ini, mula menyelesaikannya.

Lebih produktiviti!

Pemeriksaan tambahan menjadikan mypy lebih pantas, tetapi alat itu masih tidak cukup pantas. Banyak semakan tambahan mengambil masa kira-kira seminit. Sebabnya ialah import kitaran. Ini mungkin tidak akan mengejutkan sesiapa sahaja yang telah bekerja dengan pangkalan kod besar yang ditulis dalam Python. Kami mempunyai set ratusan modul, setiap satu daripadanya secara tidak langsung mengimport semua yang lain. Jika mana-mana fail dalam gelung import telah ditukar, mypy terpaksa memproses semua fail dalam gelung itu, dan selalunya mana-mana modul yang mengimport modul daripada gelung itu. Satu kitaran sedemikian ialah "kekusutan pergantungan" yang terkenal yang menyebabkan banyak masalah di Dropbox. Sebaik sahaja struktur ini mengandungi beberapa ratus modul, semasa ia diimport, secara langsung atau tidak langsung, banyak ujian, ia juga digunakan dalam kod pengeluaran.

Kami mempertimbangkan kemungkinan "merungkai" kebergantungan bulat, tetapi kami tidak mempunyai sumber untuk melakukannya. Terdapat terlalu banyak kod yang kami tidak biasa. Hasilnya, kami menghasilkan pendekatan alternatif. Kami memutuskan untuk membuat mypy berfungsi dengan cepat walaupun dalam kehadiran "kekusutan ketergantungan". Kami mencapai matlamat ini menggunakan daemon mypy. Daemon ialah proses pelayan yang melaksanakan dua ciri menarik. Pertama, ia menyimpan maklumat tentang keseluruhan pangkalan kod dalam ingatan. Ini bermakna setiap kali anda menjalankan mypy, anda tidak perlu memuatkan data cache yang berkaitan dengan beribu-ribu kebergantungan yang diimport. Kedua, beliau dengan teliti, pada peringkat unit struktur kecil, menganalisis kebergantungan antara fungsi dan entiti lain. Sebagai contoh, jika fungsi foo memanggil fungsi bar, maka wujudlah pergantungan foo daripada bar. Apabila fail berubah, daemon terlebih dahulu, secara berasingan, hanya memproses fail yang diubah. Ia kemudian melihat perubahan yang boleh dilihat secara luaran pada fail itu, seperti tandatangan fungsi yang diubah. Daemon menggunakan maklumat terperinci tentang import sahaja untuk menyemak semula fungsi tersebut yang sebenarnya menggunakan fungsi yang diubah suai. Biasanya, dengan pendekatan ini, anda perlu menyemak sangat sedikit fungsi.

Melaksanakan semua ini bukanlah mudah, kerana pelaksanaan mypy asal sangat tertumpu pada pemprosesan satu fail pada satu masa. Kami terpaksa berhadapan dengan banyak situasi sempadan, yang kejadiannya memerlukan semakan berulang dalam kes di mana sesuatu berubah dalam kod. Sebagai contoh, ini berlaku apabila kelas diberikan kelas asas baharu. Setelah kami melakukan apa yang kami mahu, kami dapat mengurangkan masa pelaksanaan kebanyakan semakan tambahan kepada beberapa saat sahaja. Ini kelihatan seperti kemenangan besar kepada kami.

Lebih produktiviti!

Bersama-sama dengan cache jauh yang saya bincangkan di atas, daemon mypy hampir menyelesaikan sepenuhnya masalah yang timbul apabila pengaturcara kerap menjalankan pemeriksaan jenis, membuat perubahan kepada sebilangan kecil fail. Walau bagaimanapun, prestasi sistem dalam kes penggunaan yang paling tidak menguntungkan masih jauh dari optimum. Permulaan mypy yang bersih boleh mengambil masa lebih 15 minit. Dan ini adalah lebih daripada yang kami akan gembira. Setiap minggu keadaan menjadi lebih teruk apabila pengaturcara terus menulis kod baharu dan menambah anotasi pada kod sedia ada. Pengguna kami masih mahukan prestasi yang lebih tinggi, tetapi kami gembira dapat bertemu dengan mereka separuh jalan.

Kami memutuskan untuk kembali kepada salah satu idea awal mengenai mypy. Iaitu, untuk menukar kod Python kepada kod C. Bereksperimen dengan Cython (sistem yang membolehkan anda menterjemah kod yang ditulis dalam Python ke dalam kod C) tidak memberikan kami sebarang kelajuan yang boleh dilihat, jadi kami memutuskan untuk menghidupkan semula idea menulis pengkompil kami sendiri. Memandangkan pangkalan kod mypy (ditulis dalam Python) sudah mengandungi semua anotasi jenis yang diperlukan, kami fikir adalah berbaloi untuk mencuba menggunakan anotasi ini untuk mempercepatkan sistem. Saya dengan cepat mencipta prototaip untuk menguji idea ini. Ia menunjukkan peningkatan lebih daripada 10 kali ganda dalam prestasi pada pelbagai penanda aras mikro. Idea kami adalah untuk menyusun modul Python kepada modul C menggunakan Cython, dan menukar anotasi jenis kepada semakan jenis masa jalan (biasanya anotasi taip diabaikan pada masa jalan dan hanya digunakan oleh sistem semakan jenis). Kami sebenarnya merancang untuk menterjemahkan pelaksanaan mypy daripada Python ke dalam bahasa yang direka bentuk untuk ditaip secara statik, yang akan kelihatan (dan, sebahagian besarnya, berfungsi) sama seperti Python. (Penghijrahan merentas bahasa seperti ini telah menjadi tradisi projek mypy. Pelaksanaan mypy asal ditulis dalam Alore, kemudian terdapat hibrid sintaksis Java dan Python).

Memfokuskan pada API sambungan CPython adalah kunci untuk tidak kehilangan keupayaan pengurusan projek. Kami tidak perlu melaksanakan mesin maya atau mana-mana perpustakaan yang mypy perlukan. Di samping itu, kami masih mempunyai akses kepada keseluruhan ekosistem Python dan semua alatan (seperti pytest). Ini bermakna kami boleh terus menggunakan kod Python yang ditafsirkan semasa pembangunan, membolehkan kami terus bekerja dengan corak yang sangat pantas untuk membuat perubahan kod dan mengujinya, dan bukannya menunggu kod untuk disusun. Nampaknya kami melakukan kerja yang hebat untuk duduk di atas dua kerusi, boleh dikatakan, dan kami menyukainya.

Pengkompil, yang kami panggil mypyc (kerana ia menggunakan mypy sebagai bahagian hadapan untuk menganalisis jenis), ternyata menjadi projek yang sangat berjaya. Secara keseluruhan, kami mencapai lebih kurang 4x kelajuan untuk larian mypy yang kerap tanpa caching. Membangunkan teras projek mypyc mengambil satu pasukan kecil Michael Sullivan, Ivan Levkivsky, Hugh Hahn, dan saya sendiri selama kira-kira 4 bulan kalendar. Jumlah kerja ini jauh lebih kecil daripada yang diperlukan untuk menulis semula mypy, contohnya, dalam C++ atau Go. Dan kami terpaksa membuat lebih sedikit perubahan pada projek itu daripada yang kami perlu buat semasa menulis semula dalam bahasa lain. Kami juga berharap kami dapat membawa mypyc ke tahap sedemikian sehingga pengaturcara Dropbox lain boleh menggunakannya untuk menyusun dan mempercepatkan kod mereka.

Untuk mencapai tahap prestasi ini, kami terpaksa menggunakan beberapa penyelesaian kejuruteraan yang menarik. Oleh itu, pengkompil boleh mempercepatkan banyak operasi dengan menggunakan konstruk C peringkat rendah yang pantas. Sebagai contoh, panggilan fungsi yang disusun diterjemahkan ke dalam panggilan fungsi C. Dan panggilan sedemikian adalah lebih pantas daripada memanggil fungsi yang ditafsirkan. Sesetengah operasi, seperti carian kamus, masih terlibat menggunakan panggilan C-API biasa daripada CPython, yang hanya lebih pantas sedikit apabila disusun. Kami dapat menghapuskan beban tambahan pada sistem yang dibuat melalui tafsiran, tetapi ini dalam kes ini hanya memberikan keuntungan kecil dari segi prestasi.

Untuk mengenal pasti operasi "lambat" yang paling biasa, kami melakukan pemprofilan kod. Berbekalkan data ini, kami cuba sama ada tweak mypyc supaya ia akan menghasilkan kod C yang lebih pantas untuk operasi sedemikian, atau menulis semula kod Python yang sepadan menggunakan operasi yang lebih pantas (dan kadangkala kami tidak mempunyai penyelesaian yang cukup mudah untuk itu atau masalah lain) . Menulis semula kod Python selalunya merupakan penyelesaian yang lebih mudah kepada masalah daripada mempunyai pengkompil secara automatik melakukan transformasi yang sama. Dalam jangka panjang, kami ingin mengautomasikan banyak transformasi ini, tetapi pada masa itu kami memberi tumpuan untuk mempercepatkan mypy dengan usaha yang minimum. Dan dalam bergerak ke arah matlamat ini, kami memotong beberapa sudut.

Perlu diteruskan ...

Pembaca yang dihormati! Apakah tanggapan anda tentang projek mypy apabila anda mengetahui kewujudannya?

Laluan untuk menyemak taip 4 juta baris kod Python. Bahagian 2
Laluan untuk menyemak taip 4 juta baris kod Python. Bahagian 2

Sumber: www.habr.com

Tambah komen