Saya adalah pengguna Python lama. Beberapa tahun yang lalu, saya mulai belajar C ++ untuk melihat apa yang bisa ditawarkan dalam hal kecepatan. Selama ini, saya akan terus menggunakan Python sebagai alat untuk prototyping. Tampaknya, ini adalah sistem yang baik: pengembangan lincah dengan Python, eksekusi cepat di C ++.
Baru-baru ini, saya telah menggunakan Python lagi dan lagi, dan belajar bagaimana menghindari semua jebakan dan anti-pola yang cepat saya gunakan di tahun-tahun sebelumnya dengan bahasa tersebut. Pemahaman saya bahwa menggunakan fitur tertentu (daftar pemahaman, penghitungan, dll.) Dapat meningkatkan kinerja.
Tetapi apakah ada batasan teknis atau fitur bahasa yang mencegah skrip Python saya menjadi secepat program setara C ++?
sumber
Jawaban:
Saya agak menabrak dinding ini sendiri ketika saya mengambil pekerjaan pemrograman Python penuh waktu beberapa tahun yang lalu. Saya suka Python, saya benar-benar melakukannya, tetapi ketika saya mulai melakukan beberapa tuning kinerja, saya mengalami beberapa kejutan kasar.
Pythonista yang ketat dapat mengoreksi saya, tetapi di sini ada beberapa hal yang saya temukan, dilukis dengan sapuan yang sangat luas.
Itu memiliki dampak kinerja, karena itu berarti ada tingkat tipuan tambahan pada saat dijalankan, selain slogging di sekitar sejumlah besar memori dibandingkan dengan bahasa lain.
Orang lain dapat berbicara dengan model eksekusi, tetapi Python adalah kompilasi-saat-runtime dan kemudian ditafsirkan, yang berarti tidak berjalan sampai ke kode mesin. Itu juga memiliki dampak kinerja. Anda dapat dengan mudah menautkan modul C atau C ++, atau menemukannya, tetapi jika Anda hanya menjalankan Python langsung, itu akan memiliki kinerja yang baik.
Sekarang, dalam tolok ukur layanan web, Python lebih baik dibandingkan dengan bahasa kompilasi-saat-runtime lainnya seperti Ruby atau PHP. Tapi itu jauh di belakang sebagian besar bahasa yang dikompilasi. Bahkan bahasa yang mengkompilasi ke bahasa perantara dan dijalankan di VM (seperti Java atau C #) melakukan banyak hal, jauh lebih baik.
Berikut ini adalah serangkaian tes benchmark yang sangat menarik yang saya rujuk sesekali:
http://www.techempower.com/benchmarks/
(Semua yang dikatakan, aku masih sangat mencintai Python, dan jika aku mendapat kesempatan untuk memilih bahasa tempatku bekerja, itu adalah pilihan pertamaku. Seringkali, aku tidak dibatasi oleh persyaratan throughput yang gila.)
sumber
__slots__
. PyPy harus melakukan jauh lebih baik dalam hal ini, tetapi saya tidak cukup tahu untuk menilai.Implementasi referensi Python adalah juru bahasa "CPython". Itu mencoba untuk menjadi cukup cepat, tetapi saat ini tidak menggunakan optimisasi lanjutan. Dan untuk banyak skenario penggunaan, ini adalah hal yang baik: Kompilasi ke beberapa kode perantara terjadi segera sebelum runtime, dan setiap kali program dijalankan kode dikompilasi lagi. Jadi waktu yang dibutuhkan untuk optimasi harus ditimbang dengan waktu yang diperoleh dengan optimasi - jika tidak ada keuntungan bersih, optimasi tidak berharga. Untuk program yang berjalan sangat lama, atau program dengan loop yang sangat ketat, menggunakan optimisasi tingkat lanjut akan bermanfaat. Namun, CPython digunakan untuk beberapa pekerjaan yang menghalangi optimasi agresif:
Skrip berjalan pendek, digunakan misalnya untuk tugas sysadmin. Banyak sistem operasi seperti Ubuntu membangun bagian yang baik dari infrastruktur mereka di atas Python: CPython cukup cepat untuk pekerjaan itu, tetapi sebenarnya tidak memiliki waktu mulai. Selama lebih cepat dari bash, itu bagus.
CPython harus memiliki semantik yang jelas, karena ini adalah implementasi referensi. Ini memungkinkan pengoptimalan sederhana seperti "mengoptimalkan implementasi foo operator" atau "kompilasi daftar pemahaman untuk bytecode lebih cepat", tetapi umumnya akan menghalangi pengoptimalan yang menghancurkan informasi, seperti fungsi inlining.
Tentu saja, ada lebih banyak implementasi Python dari pada hanya CPython:
Jython dibangun di atas JVM. JVM dapat menginterpretasikan atau mengkompilasi JIT dengan bytecode yang disediakan, dan memiliki optimasi yang dipandu profil. Itu menderita waktu start-up yang tinggi, dan perlu beberapa saat sampai JIT masuk.
PyPy adalah yang paling canggih, JITting Python VM. PyPy ditulis dalam RPython, subset terbatas dari Python. Subset ini menghapus beberapa ekspresif dari Python, tetapi memungkinkan jenis variabel apa pun untuk disimpulkan secara statis. VM yang ditulis dalam RPython kemudian dapat dipindahkan ke C, yang memberikan kinerja seperti RPython C. Namun, RPython masih lebih ekspresif daripada C, yang memungkinkan pengembangan lebih cepat dari optimasi baru. PyPy adalah contoh dari bootstrap kompiler. PyPy (bukan RPython!) Sebagian besar kompatibel dengan implementasi referensi CPython.
Cython (seperti RPython) adalah dialek Python yang tidak kompatibel dengan pengetikan statis. Ini juga mentranspilasikan ke kode C, dan dapat dengan mudah menghasilkan ekstensi C untuk juru bahasa CPython.
Jika Anda bersedia menerjemahkan kode Python Anda ke Cython atau RPython, maka Anda akan mendapatkan kinerja seperti C. Namun, mereka seharusnya tidak dipahami sebagai "subset dari Python", tetapi lebih sebagai "C with Pythonic syntax". Jika Anda beralih ke PyPy, kode vanilla Python Anda akan mendapatkan peningkatan kecepatan yang cukup besar, tetapi juga tidak akan dapat berinteraksi dengan ekstensi yang ditulis dalam C atau C ++.
Tetapi properti atau fitur apa yang mencegah vanilla Python mencapai tingkat kinerja seperti C, selain waktu start-up yang lama?
Kontributor dan pendanaan. Tidak seperti Java atau C #, tidak ada perusahaan mengemudi tunggal di belakang bahasa dengan minat untuk membuat bahasa ini menjadi yang terbaik di kelasnya. Ini membatasi pengembangan terutama untuk sukarelawan, dan hibah sesekali.
Mengikat terlambat dan kurangnya pengetikan statis. Python memungkinkan kita untuk menulis omong kosong seperti ini:
Dengan Python, variabel apa saja dapat dipindahkan kapan saja. Ini mencegah caching atau inlining; akses apa pun harus melalui variabel. Tipuan ini membebani kinerja. Tentu saja: jika kode Anda tidak melakukan hal-hal gila sehingga setiap variabel dapat diberikan tipe definitif sebelum kompilasi dan setiap variabel ditugaskan hanya sekali, maka - secara teori - model eksekusi yang lebih efisien dapat dipilih. Bahasa dengan pemikiran ini akan memberikan beberapa cara untuk menandai pengidentifikasi sebagai konstanta, dan setidaknya memungkinkan anotasi jenis opsional (“pengetikan bertahap”).
Model objek yang dipertanyakan. Kecuali slot digunakan, sulit untuk mengetahui bidang mana yang dimiliki suatu objek (objek Python pada dasarnya adalah tabel bidang hash). Dan bahkan begitu kita di sana, kita masih tidak tahu apa jenis bidang ini. Ini mencegah representasi objek sebagai struct yang padat, seperti halnya dalam C ++. (Tentu saja, representasi objek C ++ juga tidak ideal: karena sifatnya yang seperti struct, bahkan bidang pribadi termasuk antarmuka publik dari suatu objek.)
Pengumpulan sampah. Dalam banyak kasus, GC dapat dihindari sepenuhnya. C ++ memungkinkan kita untuk statis mengalokasikan benda yang hancur secara otomatis ketika lingkup saat ini yang tersisa:
Type instance(args);
. Sampai saat itu, objek itu hidup dan dapat dipinjamkan ke fungsi lain. Ini biasanya dilakukan melalui "pass-by-reference". Bahasa seperti Rust memungkinkan kompiler untuk memeriksa secara statis bahwa tidak ada pointer ke objek seperti itu melebihi umur objek. Skema manajemen memori ini benar-benar dapat diprediksi, sangat efisien, dan sesuai untuk sebagian besar kasus tanpa grafik objek yang rumit. Sayangnya, Python tidak dirancang dengan manajemen memori dalam pikiran. Secara teori, analisis pelarian dapat digunakan untuk menemukan kasus di mana GC dapat dihindari. Dalam prakteknya, rantai metode sederhana sepertifoo().bar().baz()
harus mengalokasikan sejumlah besar objek berumur pendek di heap (GC generasi adalah salah satu cara untuk menjaga masalah ini kecil).Dalam kasus lain, pemrogram mungkin sudah mengetahui ukuran akhir dari beberapa objek seperti daftar. Sayangnya, Python tidak menawarkan cara untuk mengomunikasikan ini ketika membuat daftar baru. Sebagai gantinya, item baru akan didorong ke ujung, yang mungkin membutuhkan realokasi beberapa kali. Beberapa catatan:
Daftar ukuran tertentu dapat dibuat seperti
fixed_size = [None] * size
. Namun, memori untuk objek di dalam daftar itu harus dialokasikan secara terpisah. Kontras C ++, yang bisa kita lakukanstd::array<Type, size> fixed_size
.Array paket dari tipe asli tertentu dapat dibuat dengan Python melalui
array
modul builtin. Selain itu,numpy
menawarkan representasi buffer data yang efisien dengan bentuk khusus untuk tipe numerik asli.Ringkasan
Python dirancang untuk kemudahan penggunaan, bukan untuk kinerja. Desainnya membuat membuat implementasi yang sangat efisien menjadi agak sulit. Jika programmer tidak menggunakan fitur yang bermasalah, maka kompiler yang memahami idiom yang tersisa akan dapat memancarkan kode efisien yang dapat menyaingi C dalam kinerja.
sumber
Iya. Masalah utama adalah bahasanya didefinisikan sebagai dinamis - yaitu, Anda tidak pernah tahu apa yang Anda lakukan sampai Anda akan melakukannya. Yang membuatnya sangat sulit untuk menghasilkan kode mesin yang efisien, karena Anda tidak tahu apa yang harus menghasilkan kode mesin untuk . Kompiler JIT dapat melakukan beberapa pekerjaan di bidang ini tetapi tidak pernah sebanding dengan C ++ karena kompiler JIT tidak bisa menghabiskan waktu dan memori berjalan, karena itu waktu dan memori yang tidak Anda habiskan untuk menjalankan program Anda, dan ada batasan keras tentang apa mereka dapat mencapai tanpa melanggar semantik bahasa dinamis.
Saya tidak akan mengklaim bahwa ini merupakan tradeoff yang tidak dapat diterima. Tapi itu mendasar untuk sifat Python bahwa implementasi nyata tidak akan pernah secepat implementasi C ++.
sumber
Ada tiga faktor utama yang mempengaruhi kinerja semua bahasa dinamis, beberapa lebih dari yang lain.
Untuk C / C ++ biaya relatif dari 3 faktor ini hampir nol. Instruksi dijalankan langsung oleh prosesor, pengiriman membutuhkan paling banyak satu atau dua tipuan, memori tumpukan tidak pernah dialokasikan kecuali Anda mengatakannya. Kode yang ditulis dengan baik dapat mendekati bahasa assembly.
Untuk C # / Java dengan kompilasi JIT dua yang pertama rendah tetapi memori sampah yang dikumpulkan memiliki biaya. Kode yang ditulis dengan baik dapat mendekati 2x C / C ++.
Untuk Python / Ruby / Perl biaya ketiga faktor ini relatif tinggi. Pikirkan 5x dibandingkan dengan C / C ++ atau lebih buruk. (*)
Ingat bahwa kode pustaka runtime mungkin ditulis dalam bahasa yang sama dengan program Anda dan memiliki batasan kinerja yang sama.
(*) Karena kompilasi Just-In_Time (JIT) diperluas ke bahasa-bahasa ini, mereka juga akan mendekati (biasanya 2x) kecepatan kode C / C ++ yang ditulis dengan baik.
Juga harus dicatat bahwa begitu kesenjangannya sempit (antara bahasa yang bersaing), maka perbedaan didominasi oleh algoritma dan detail implementasi. Kode JIT dapat mengalahkan C / C ++ dan C / C ++ dapat mengalahkan bahasa assembly karena lebih mudah untuk menulis kode yang baik.
sumber
Hash
kelas Rubinius (salah satu datastructures inti di Ruby) ditulis dalam Ruby, dan kinerjanya sebanding, kadang-kadang bahkan lebih cepat, daripadaHash
kelas YARV yang ditulis dalam C. Dan salah satu alasannya adalah sebagian besar runtime Rubinius sistem ditulis dalam Ruby, sehingga mereka dapat ...Tidak. Ini hanya masalah uang dan sumber daya yang dituangkan untuk membuat C ++ berjalan cepat vs. uang dan sumber daya yang dituangkan untuk membuat Python berjalan cepat.
Misalnya, ketika Self VM keluar, itu bukan hanya bahasa OO dinamis tercepat, itu adalah periode bahasa OO tercepat. Meskipun menjadi bahasa yang sangat dinamis (lebih dari Python, Ruby, PHP atau JavaScript, misalnya), itu lebih cepat daripada sebagian besar implementasi C ++ yang tersedia.
Tapi kemudian Sun membatalkan proyek Self (bahasa OO tujuan umum yang matang untuk mengembangkan sistem besar) untuk fokus pada bahasa scripting kecil untuk menu animasi di kotak atas TV (Anda mungkin pernah mendengar tentang itu, itu disebut Java), tidak ada lebih banyak dana. Pada saat yang sama, Intel, IBM, Microsoft, Sun, Metrowerks, HP et al. menghabiskan banyak uang dan sumber daya membuat C ++ cepat. Produsen CPU menambahkan fitur ke chip mereka untuk membuat C ++ lebih cepat. Sistem Operasi ditulis atau dimodifikasi untuk membuat C ++ lebih cepat. Jadi, C ++ cepat.
Saya tidak terlalu terbiasa dengan Python, saya lebih merupakan orang Ruby, jadi saya akan memberikan contoh dari Ruby:
Hash
kelas (setara dalam fungsi dan pentingnya dengandict
Python) dalam implementasi Rubinius Ruby ditulis dalam 100% Ruby murni; namun itu bersaing dengan baik dan kadang-kadang bahkan mengungguliHash
kelas dalam YARV yang ditulis dengan tangan-dioptimalkan C. Dan dibandingkan dengan beberapa sistem Lisp atau Smalltalk komersial (atau Self VM yang disebutkan sebelumnya), kompiler Rubinius bahkan tidak sepintar itu .Tidak ada yang melekat pada Python yang membuatnya lambat. Ada beberapa fitur dalam prosesor dan sistem operasi saat ini yang melukai Python (mis. Memori virtual dikenal buruk untuk kinerja pengumpulan sampah). Ada fitur yang membantu C ++ tetapi tidak membantu Python (CPU modern mencoba untuk menghindari kesalahan cache, karena mereka sangat mahal. Sayangnya, menghindari kesalahan cache sulit ketika Anda memiliki OO dan polimorfisme. Sebaliknya, Anda harus mengurangi biaya cache CPU Azul Vega, yang dirancang untuk Java, melakukan hal ini.)
Jika Anda menghabiskan banyak uang, penelitian dan sumber daya untuk membuat Python cepat, seperti yang dilakukan untuk C ++, dan Anda menghabiskan banyak uang, penelitian dan sumber daya untuk membuat sistem operasi yang membuat program Python berjalan cepat seperti yang dilakukan untuk C ++ dan Anda menghabiskan banyak uang, penelitian dan sumber daya untuk membuat CPU yang membuat program Python berjalan cepat seperti yang dilakukan untuk C ++, maka tidak ada keraguan dalam pikiran saya bahwa Python dapat mencapai kinerja yang sebanding dengan C ++.
Kami telah melihat dengan ECMAScript apa yang dapat terjadi jika hanya satu pemain yang serius tentang kinerja. Dalam setahun, pada dasarnya kami mengalami peningkatan kinerja 10 kali lipat untuk semua vendor besar.
sumber