Apa fitur semantik dari Python (dan bahasa dinamis lainnya) yang berkontribusi terhadap kelambatannya?

26

Saya tidak tahu betul Python. Saya mencoba memahami dengan lebih tepat fitur persis apa dari bahasa dinamis (à la Python, Lua, Skema, Perl, Ruby, ....) yang memaksa implementasi mereka lambat.

Sebagai contoh, Lua 5.3 mesin metatable secara intuitif akan membuat Lua sangat lambat, tetapi dalam praktiknya Lua dikabarkan cukup cepat (dan lebih cepat daripada Python).

Juga, saya memiliki intuisi (mungkin yang salah) bahwa karena pada prosesor saat ini memori jauh lebih lambat daripada perhitungan mentah (akses memori dengan cache miss membutuhkan waktu yang sama dengan ratusan operasi aritmatika), pengecekan tipe dinamis (ala if (value->type != INTEGER_TAG) return;dalam C Parlance) bisa berjalan cukup cepat.

Tentu saja, seluruh analisis program (seperti yang dilakukan implementasi Skema Stalin ) dapat membuat implementasi bahasa yang dinamis sebagai penerjemah berjalan cepat, tetapi mari kita berpura-pura saya tidak punya waktu untuk merancang penganalisa seluruh program pada awalnya.

(Saya semacam merancang bahasa dinamis di monitor MELT saya , dan beberapa di antaranya akan diterjemahkan ke C)

Basile Starynkevitch
sumber
1
Tips Kinerja Lua , yang menjelaskan mengapa beberapa program Lua lambat dan cara memperbaikinya.
Robert Harvey

Jawaban:

24

Apa fitur semantik dari Python (dan bahasa dinamis lainnya) yang berkontribusi terhadap kelambatannya?

Tidak ada

Kinerja implementasi bahasa adalah fungsi dari uang, sumber daya, dan tesis PhD, bukan fitur bahasa. Self jauh lebih dinamis daripada Smalltalk dan sedikit lebih dinamis daripada Python, Ruby, ECMAScript, atau Lua, dan memiliki VM yang mengungguli semua Lisp dan Smalltalk VM yang ada (pada kenyataannya, distribusi Self dikirimkan bersama dengan interpreter Smalltalk kecil yang ditulis dalam Self , dan bahkan itu lebih cepat dari kebanyakan VM Smalltalk yang ada), dan bersaing dengan, dan terkadang bahkan lebih cepat daripada implementasi C ++ saat itu.

Kemudian, Sun menghentikan pendanaan Self, dan IBM, Microsoft, Intel, and Co. mulai mendanai C ++, dan tren berbalik. Pengembang mandiri meninggalkan Sun untuk memulai perusahaan mereka sendiri, di mana mereka menggunakan teknologi yang dikembangkan untuk VM Self untuk membangun salah satu VM Smalltalk tercepat (VM Animorphic), dan kemudian Sun membeli kembali perusahaan itu, dan versi yang sedikit dimodifikasi dari bahwa Smalltalk VM sekarang lebih dikenal dengan nama "HotSpot JVM". Ironisnya, programmer Java memandang rendah pada bahasa dinamis karena "lambat", padahal sebenarnya, Javalambat sampai mengadopsi teknologi bahasa dinamis. (Ya, itu benar: HotSpot JVM pada dasarnya adalah Smalltalk VM. Verifier bytecode melakukan banyak pengecekan tipe, tetapi begitu bytecode diterima oleh verifier, VM, dan terutama optimizer dan JIT tidak benar-benar melakukan banyak yang tertarik dengan tipe statis!)

CPython sama sekali tidak melakukan banyak hal yang membuat bahasa dinamis (atau lebih tepatnya pengiriman dinamis) cepat: kompilasi dinamis (JIT), optimisasi dinamis, inferensi spekulatif, optimasi adaptif, de-optimasi dinamis, umpan balik / inferensi tipe dinamis. Ada juga masalah yang hampir seluruh inti dan pustaka standar ditulis dalam C, yang berarti bahwa bahkan jika Anda membuat Python 100x lebih cepat tiba-tiba, itu tidak akan banyak membantu Anda, karena sekitar 95% dari kode dieksekusi oleh Program python adalah C, bukan Python. Jika semuanya ditulis dalam Python, bahkan speedup moderat akan membuat efek longsoran salju, di mana algoritma menjadi lebih cepat, dan struktur data inti menjadi lebih cepat, tetapi tentu saja struktur data inti juga digunakan dalam algoritma, dan algoritma inti dan data inti struktur digunakan di tempat lain,

Ada beberapa hal yang terkenal buruk untuk bahasa OO yang dikelola memori (dinamis atau tidak) dalam sistem saat ini. Memori Virtual dan Perlindungan Memori dapat menjadi pembunuh untuk kinerja pengumpulan sampah pada khususnya, dan kinerja sistem pada umumnya. Dan itu benar-benar tidak perlu dalam bahasa yang aman-memori: mengapa melindungi terhadap akses memori ilegal ketika tidak ada memori yang mengakses dalam bahasa untuk memulai? Azul telah menemukan untuk menggunakan MMU modern yang kuat (Intel Nehalem dan yang lebih baru, dan AMD yang setara) untuk membantu pengumpulan sampah alih-alih menghalanginya, tetapi meskipun didukung oleh CPU, subsistem memori saat ini dari OS mainstream tidak cukup kuat untuk memungkinkan ini (itulah sebabnya Azul's JVM sebenarnya berjalan tervirtualisasi pada bare metal di samping OS, bukan di dalamnya).

Dalam proyek Singularity OS, Microsoft telah mengukur dampak ~ 30% pada kinerja sistem ketika menggunakan perlindungan MMU alih-alih sistem tipe untuk pemisahan proses.

Hal lain yang diperhatikan Azul ketika membangun CPU Java khusus mereka adalah bahwa CPU arus utama modern fokus pada hal yang sepenuhnya salah ketika mencoba untuk mengurangi biaya cache yang hilang: mereka mencoba untuk mengurangi jumlah cache yang hilang melalui hal-hal seperti prediksi cabang, pengambilan memori, dan seterusnya. Tetapi, dalam program OO yang sangat polimorfik, pola akses pada dasarnya pseudo-acak, tidak ada yang bisa diprediksi. Jadi, semua transistor itu hanya terbuang sia-sia, dan yang harus dilakukan adalah mengurangi biaya setiap cache yang hilang. (Total biaya adalah #misses * biaya, arus utama mencoba membawa yang pertama turun, Azul yang kedua.) Akselerator Java Compute Azul dapat memiliki 20.000 cache cache yang hilang secara bersamaan dalam penerbangan dan masih membuat kemajuan.

Ketika Azul memulai, mereka pikir mereka akan mengambil beberapa komponen I / O yang tidak tersedia dan mendesain inti CPU khusus mereka sendiri, tetapi apa yang sebenarnya perlu mereka lakukan adalah kebalikannya: mereka mengambil standar yang agak standar. rak 3-address inti RISC dan merancang pengontrol memori, MMU, dan subsistem cache mereka sendiri.

tl; dr : "Kelambatan" Python bukan properti bahasa tetapi a) implementasi naif (primer), dan b) fakta bahwa CPU dan OS modern dirancang khusus untuk membuat C berjalan cepat, dan fitur-fiturnya miliki untuk C tidak membantu (cache) atau bahkan secara aktif menyakiti (virtual memory) kinerja Python.

Dan Anda dapat memasukkan hampir semua bahasa yang dikelola memori dengan polimorfisme ad-hoc yang dinamis di sini ... ketika datang ke tantangan implementasi yang efisien, bahkan Python dan Java cukup banyak "bahasa yang sama".

Jörg W Mittag
sumber
Apakah Anda memiliki tautan atau referensi untuk Azul?
Basile Starynkevitch
4
Saya tidak setuju bahwa semantik bahasa tidak mempengaruhi kemampuannya untuk diimplementasikan secara efisien. Ya, implementasi JIT yang baik dengan optimasi dan analisis kelas satu dapat membuat peningkatan besar pada kinerja suatu bahasa, tetapi pada akhirnya ada aspek-aspek tertentu dari semantik yang tak terhindarkan akan berakhir menjadi hambatan. Apakah itu persyaratan C untuk pengucilan pointer yang ketat atau persyaratan Python bahwa operasi daftar harus dilakukan secara atomis ada beberapa keputusan semantik tertentu yang akhirnya akan menghambat kinerja beberapa aplikasi.
Jules
1
Sebagai tambahan ... apakah Anda memiliki referensi untuk peningkatan 30% untuk Singularity? Saya telah menjadi penganjur OS perlindungan berbasis bahasa selama bertahun-tahun, tetapi belum pernah melihat angka itu sebelumnya, dan menemukan itu cukup mengejutkan (angka yang saya lihat di masa lalu mendekati 10%) dan bertanya-tanya apa mereka lakukan untuk mendapatkan banyak perbaikan ...
Jules
5
@MasonWheeler: Karena hanya ada implementasi Python jelek di luar sana. Tidak ada implementor Python yang menghabiskan sebagian kecil dari uang, orang, riset, dan sumber daya yang dikeluarkan oleh IBM, Sun, Oracle, Google dan Co. untuk J9, JRockit, HotSpot, dan Co. Semua 5 implementasi Python yang digabungkan mungkin bahkan tidak memiliki tenaga yang dihabiskan Oracle hanya untuk pengumpul sampah. IBM sedang mengerjakan implementasi Python berdasarkan Eclipse OMR (kerangka kerja bersumber terbuka VM yang diekstraksi yang diambil dari J9), saya berani bertaruh bahwa kinerjanya akan baik dalam urutan besarnya J9
Jörg W Mittag
2
Sebagai catatan C lambat dibandingkan dengan Fortran untuk pekerjaan numerik karena Fortran memberlakukan aliasing yang ketat sehingga pengoptimal bisa lebih agresif.
Michael Shopsin
8

Meskipun implementasi Python saat ini (yang tidak memiliki banyak optimisasi yang dilakukan oleh bahasa dinamis lainnya, misalnya implementasi Javascript modern dan, seperti yang Anda tunjukkan, Lua) adalah sumber dari sebagian besar masalahnya, ia memiliki beberapa masalah semantik yang akan membuatnya menjadi sulit bagi implementasi untuk bersaing dengan bahasa lain, setidaknya di bidang tertentu. Beberapa yang patut dipertimbangkan khususnya:

  • Operasi daftar dan kamus diperlukan oleh definisi bahasa sebagai atom. Ini berarti bahwa kecuali jika kompiler JIT dapat membuktikan bahwa tidak ada referensi ke objek daftar telah lolos dari utasnya saat ini (analisis yang sulit dalam banyak kasus dan tidak mungkin dalam kasus umum) itu harus memastikan akses ke objek tersebut serial (misalnya melalui penguncian). Implementasi CPython menyelesaikan masalah ini dengan menggunakan "kunci interpreter global" yang terkenal buruk yang mencegah kode Python digunakan secara efektif dalam lingkungan multi-pemrosesan dengan teknik multi-thread, dan sementara solusi lain memungkinkan, mereka semua memiliki masalah kinerja.

  • Python tidak memiliki mekanisme untuk menentukan penggunaan objek nilai; semuanya ditangani dengan referensi, menambahkan tipuan ekstra di mana itu tidak selalu diperlukan. Meskipun kompiler JIT dimungkinkan untuk menyimpulkan objek nilai dalam beberapa kasus dan secara otomatis mengoptimalkannya, tidak mungkin untuk melakukannya secara umum dan oleh karena itu kode yang tidak ditulis dengan hati-hati untuk memastikan optimalisasi dimungkinkan (yang agak merupakan seni hitam) akan menderita.

  • Python memiliki evalfungsi, yang berarti bahwa kompiler JIT tidak dapat membuat asumsi tentang tindakan yang tidak terjadi, bahkan jika ia melakukan analisis seluruh program, selama evaldigunakan sekali. Sebagai contoh, kompilator Python tidak dapat berasumsi bahwa suatu kelas tidak memiliki subclass dan oleh karena itu devirtualize panggilan metode, karena asumsi itu nantinya dapat dinegasikan melalui panggilan ke eval. Alih-alih itu harus melakukan pemeriksaan tipe dinamis untuk memastikan bahwa asumsi yang dibuat oleh kode asli yang dikompilasi belum dibatalkan sebelum pelaksanaan kode itu.

Jules
sumber
3
Poin terakhir dapat dikurangi dengan evalmemicu kompilasi ulang dan / atau de-optimasi.
Jörg W Mittag
4
Omong-omong, itu juga tidak unik untuk Python. Java (atau lebih tepatnya JVM) memiliki pemuatan kode dinamis, dan tautan dinamis, sehingga Analisis Hirarki Kelas setara dengan memecahkan Masalah Pemutusan di sana juga. Namun, HotSpot dengan senang hati secara spekulatif menggarisbawahi metode polimorfik, dan jika sesuatu dalam hierarki kelas berubah, yah, itu hanya akan menurunkan mereka kembali.
Jörg W Mittag