Apa tantangan yang terkait dengan mengetik secara tertulis kompiler untuk bahasa yang diketik secara dinamis?

9

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?

syntagma
sumber
Dalam hal ini pengetikan dinamis bukan masalah terbesar. Untuk python itu adalah pelingkupan dinamis.
SK-logic
Perlu dicatat bahwa orang lain berpendapat bahwa membangun pengetikan dinamis ke platform adalah jawaban yang tepat di sini. Microsoft memasukkan banyak uang ke dalam DLR untuk alasan yang tepat ini — dan NeXT / Apple sudah setengah jalan dalam kereta musik itu selama beberapa dekade. Itu tidak membantu CPython, tetapi IronPython membuktikan bahwa Anda dapat secara efektif mengkompilasi Python, dan PyPy membuktikan bahwa Anda tidak perlu melakukannya.
abarnert
2
@ SK-logika Pelingkupan dinamis dalam Python? Terakhir saya cek, semua konstruk dalam bahasa menggunakan scoping lexical.
1
@ SK-logic Anda dapat secara dinamis membuat kode dan menjalankannya, tetapi kode itu juga berjalan dengan cakupan leksikal. Untuk setiap variabel tunggal dalam program Python, Anda dapat dengan mudah menentukan ruang lingkup variabel milik hanya dengan memeriksa AST. Anda mungkin memikirkan execpernyataan 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).
1
@ SK-logic Satu-satunya hal yang menjadi detail implementasi bagi saya adalah perubahan untuk mengembalikan nilai locals()persistensi panggilan locals. Apa yang didokumentasikan dan jelas bukan detail implementasi adalah bahwa tidak genap localsatau globalsdapat 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, evaldan execjelas bukan detail implementasi baik - lihat jawaban saya!)

Jawaban:

16

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
4

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:

i = 2
x = 3 + i

def prn(s):
    print(s)

i = prn
i(x)

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 fooselalu berupa integer; kemudian PyPy dapat mengoptimalkan kode yang mencari tipe foopada setiap pass melalui loop, dan sering kali bahkan dapat menghilangkan objek Python yang mewakili integer, dan foobisa 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.

steveha
sumber
Unladen Swallow bukan tentang kompilasi statis atau tipe statis; tujuan akhirnya adalah secara efektif untuk memindahkan juru bahasa CPython, dengan semua ke-dinamis-annya, ke LLVM, dengan JIT baru yang mewah (seperti Parrot, atau DLR untuk .NET ... atau PyPy, sungguh), meskipun apa yang sebenarnya mereka hasilkan akhirnya lakukan adalah menemukan banyak optimasi lokal di dalam CPython (beberapa di antaranya membuatnya menjadi arus utama 3.x). Shedskin mungkin adalah proyek yang Anda pikirkan menggunakan inferensi tipe statis untuk mengkompilasi Python secara statis (meskipun ke C ++, tidak langsung ke kode asli).
abarnert
Salah satu penulis Unladen Swallow, Reid Kleckner, memposting Unladen Swallow Retrospective , yang mungkin layak dibaca dalam konteks ini, meskipun sebenarnya lebih tentang tantangan manajemen dan sponsor daripada yang teknis.
abarnert
0

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 #.

Ira Baxter
sumber