Dalam pembicaraan ini , Guido van Rossum berbicara (27:30) tentang upaya untuk menulis kompiler untuk kode Python, mengomentarinya mengatakan:
ternyata tidak begitu mudah untuk menulis kompiler yang mempertahankan semua sifat pengetikan dinamis yang bagus dan juga menjaga kebenaran semantik dari program Anda, sehingga itu benar-benar melakukan hal yang sama tidak peduli apa pun jenis keanehan yang Anda lakukan di suatu tempat di bawah selimut dan benar-benar berjalan lebih cepat
Apa saja (mungkin) tantangan yang terkait dengan mengetik secara tertulis sebuah kompiler untuk bahasa yang diketik secara dinamis seperti Python?
exec
pernyataan itu , yang hilang sejak 3.0 dan karenanya di luar pertimbangan saya (dan mungkin Guido, karena pembicaraannya dari 2012). Bisakah Anda memberi contoh? Dan definisi Anda tentang "pelingkupan dinamis", jika [berbeda dari milik saya] (en.wikipedia.org/wiki/Dynamic_scoping).locals()
persistensi panggilanlocals
. Apa yang didokumentasikan dan jelas bukan detail implementasi adalah bahwa tidak genaplocals
atauglobals
dapat berubah di mana ruang lingkup setiap variabel dicari. Untuk setiap penggunaan variabel, cakupan yang dirujuk ditentukan secara statis. Yang membuatnya jelas dicakup secara leksikal. (Dan btw,eval
danexec
jelas bukan detail implementasi baik - lihat jawaban saya!)Jawaban:
Anda terlalu menyederhanakan pernyataan Guido dalam mengungkapkan pertanyaan Anda. Masalahnya bukan menulis kompiler untuk bahasa yang diketik secara dinamis. Masalahnya adalah menulis yang (kriteria 1) selalu benar, (kriteria 2) terus mengetik dinamis, dan (kriteria 3) terasa lebih cepat untuk sejumlah kode yang signifikan.
Sangat mudah untuk mengimplementasikan 90% (kriteria gagal 1) dari Python dan secara konsisten cepat dalam hal itu. Demikian pula, mudah untuk membuat varian Python yang lebih cepat dengan pengetikan statis (kriteria 2 gagal). Menerapkan 100% juga mudah (sejauh menerapkan bahasa yang kompleks itu mudah), tetapi sejauh ini setiap cara mudah untuk mengimplementasikannya ternyata relatif lambat (kriteria 3 gagal).
Mengimplementasikan interpreter plus JIT yang benar, mengimplementasikan seluruh bahasa, dan lebih cepat untuk beberapa kode ternyata layak, meskipun secara signifikan lebih sulit (lih. PyPy) dan hanya demikian jika Anda mengotomatiskan pembuatan kompiler JIT (Psyco melakukannya tanpa itu , tetapi sangat terbatas pada kode apa yang bisa mempercepat). Tetapi perhatikan bahwa ini secara eksplisit di luar cakupan, karena kita berbicara tentang statisKompiler (alias di muka). Saya hanya menyebutkan ini untuk menjelaskan mengapa pendekatannya tidak bekerja untuk kompiler statis (atau setidaknya tidak ada contoh tandingan): Pertama-tama harus menginterpretasikan dan mengamati program, kemudian menghasilkan kode untuk iterasi tertentu dari loop (atau kode linier lain) path), kemudian optimalkan itu berdasarkan pada asumsi yang hanya berlaku untuk iterasi tertentu (atau setidaknya, tidak untuk semua iterasi yang mungkin). Harapannya adalah bahwa banyak eksekusi kemudian dari kode itu juga akan cocok dengan harapan dan dengan demikian mendapat manfaat dari optimasi. Beberapa cek (relatif murah) ditambahkan untuk memastikan kebenaran. Untuk melakukan semua ini, Anda memerlukan gagasan tentang apa yang menjadi spesialisasi, dan implementasi yang lambat namun umum untuk kembali. Kompiler AOT tidak memiliki keduanya. Mereka tidak bisa berspesialisasi sama sekaliberdasarkan kode yang tidak dapat mereka lihat (mis. kode yang dimuat secara dinamis), dan mengkhususkan secara sembrono berarti menghasilkan lebih banyak kode, yang memiliki sejumlah masalah (pemanfaatan icache, ukuran biner, waktu kompilasi, cabang tambahan).
Menerapkan compiler AOT yang benar mengimplementasikan seluruh bahasa juga relatif mudah: Menghasilkan kode yang panggilan ke runtime untuk melakukan apa interpreter akan lakukan ketika makan dengan kode ini. Nuitka (kebanyakan) melakukan ini. Namun, ini tidak menghasilkan banyak manfaat kinerja (kriteria gagal 3), karena Anda masih harus melakukan pekerjaan yang sama tidak perlunya dengan penerjemah, kecuali mengirimkan bytecode ke blok kode C yang melakukan apa yang Anda kompilasi. Tetapi itu hanya biaya yang agak kecil - cukup signifikan sehingga layak dioptimalkan dalam penerjemah yang ada, tetapi tidak cukup signifikan untuk membenarkan implementasi yang sama sekali baru dengan masalahnya sendiri.
Apa yang dibutuhkan untuk memenuhi ketiga kriteria tersebut? Kami tidak tahu. Ada beberapa skema analisis statis yang dapat mengekstrak beberapa informasi tentang jenis beton, aliran kontrol, dll. Dari program Python. Yang menghasilkan data akurat di luar lingkup blok dasar tunggal sangat lambat dan perlu melihat keseluruhan program, atau setidaknya sebagian besar. Namun, Anda tidak dapat berbuat banyak dengan informasi itu, selain mungkin mengoptimalkan beberapa operasi pada tipe builtin.
Kenapa begitu? Terus terang, kompiler menghapus kemampuan untuk mengeksekusi kode Python yang dimuat saat runtime (kriteria gagal 1), atau tidak membuat asumsi yang dapat dibatalkan oleh kode Python sama sekali. Sayangnya, itu mencakup hampir semua yang berguna untuk mengoptimalkan program: Global termasuk fungsi dapat dipulihkan, kelas dapat dimutasi atau diganti seluruhnya, modul dapat dimodifikasi secara sewenang-wenang juga, impor dapat dibajak dengan beberapa cara, dll. Sebuah string dilewatkan ke
eval
,exec
,,__import__
atau berbagai fungsi lainnya, dapat melakukan semua itu. Akibatnya, itu berarti hampir tidak ada optimasi besar yang dapat diterapkan, menghasilkan sedikit manfaat kinerja (kriteria gagal 3). Kembali ke paragraf di atas.sumber
Masalah yang paling sulit adalah mencari tahu apa jenis segala sesuatu pada waktu tertentu.
Dalam bahasa statis seperti C atau Java, setelah Anda melihat deklarasi tipe, Anda tahu apa objek itu dan apa yang bisa dilakukan. Jika variabel dideklarasikan
int
, itu adalah bilangan bulat. Sebagai contoh, ini bukan referensi fungsi yang bisa dipanggil.Dengan Python, bisa jadi. Ini Python yang mengerikan, tetapi legal:
Sekarang, contoh ini cukup bodoh, tetapi menggambarkan ide umum.
Lebih realistis, Anda dapat mengganti fungsi bawaan dengan fungsi yang ditentukan pengguna yang melakukan sesuatu yang sedikit berbeda (seperti versi yang mencatat argumennya saat Anda menyebutnya).
PyPy menggunakan kompilasi Just-In-Time setelah menonton apa kode sebenarnya, dan ini memungkinkan PyPy mempercepat banyak hal. PyPy dapat menonton loop, dan memverifikasi bahwa setiap kali loop berjalan, variabel
foo
selalu berupa integer; kemudian PyPy dapat mengoptimalkan kode yang mencari tipefoo
pada setiap pass melalui loop, dan sering kali bahkan dapat menghilangkan objek Python yang mewakili integer, danfoo
bisa saja menjadi nomor yang duduk di register pada CPU. Inilah cara PyPy bisa lebih cepat dari CPython; CPython melakukan pencarian tipe secepat mungkin, tetapi bahkan tidak melakukan pencarian bahkan lebih cepat.Saya tidak tahu detailnya, tapi saya ingat ada proyek bernama Unladen Swallow yang mencoba menerapkan teknologi kompiler statis untuk mempercepat Python (menggunakan LLVM). Anda mungkin ingin mencari Google untuk Unladen Swallow dan melihat apakah Anda dapat menemukan diskusi tentang mengapa itu tidak berhasil seperti yang mereka harapkan.
sumber
Seperti jawaban lain mengatakan, masalah utama adalah mencari tahu jenis informasi. Sejauh Anda dapat melakukannya secara statis, Anda dapat langsung menghasilkan kode yang baik.
Tetapi bahkan jika Anda tidak dapat melakukannya secara statis, Anda masih dapat menghasilkan kode yang masuk akal, hanya pada saat runtime, ketika Anda mendapatkan informasi jenis aktual . Informasi ini seringkali berubah menjadi stabil, atau paling tidak memiliki beberapa nilai berbeda untuk entitas tertentu pada titik kode tertentu. The bahasa pemrograman DIRI memelopori banyak ide dari koleksi jenis runtime agresif dan generasi kode runtime. Gagasannya banyak digunakan dalam kompiler berbasis JIT modern seperti Java dan C #.
sumber