Mengapa bahasa tingkat tinggi sepertinya tidak pernah mencapai bahasa tingkat rendah dalam hal kecepatan? Contoh bahasa tingkat tinggi adalah Python, Haskell, dan Java. Bahasa tingkat rendah akan lebih sulit untuk didefinisikan, tetapi katakanlah C. Perbandingan dapat ditemukan di seluruh Internet 1 dan mereka semua setuju bahwa C jauh lebih cepat, kadang-kadang dengan faktor 10 atau lebih.
Apa yang menyebabkan perbedaan besar dalam kinerja dan mengapa bahasa tingkat tinggi tidak dapat mengejar ketinggalan?
Awalnya saya percaya bahwa ini adalah kesalahan semua kompiler dan bahwa hal-hal akan membaik di masa depan, tetapi beberapa bahasa tingkat tinggi yang paling populer telah ada selama beberapa dekade dan mereka masih tertinggal ketika datang ke kecepatan. Tidak bisakah mereka mengkompilasi ke pohon sintaksis yang mirip dengan C dan kemudian mengikuti prosedur yang sama yang menghasilkan kode mesin? Atau mungkin itu ada hubungannya dengan sintaks itu sendiri?
Contoh:
Jawaban:
Membongkar beberapa mitos
Tidak ada bahasa cepat. Suatu bahasa umumnya dapat menghasilkan kode cepat, tetapi bahasa yang berbeda akan unggul pada tolok ukur yang berbeda. Kami dapat menentukan peringkat bahasa pada serangkaian tolok ukur cacat tertentu, tetapi tidak dapat memeringkat bahasa dalam ruang hampa.
Kode C cenderung lebih cepat karena orang yang membutuhkan setiap inci kinerja menggunakan C. Statistik bahwa C lebih cepat "dengan faktor 10" mungkin tidak akurat, karena mungkin orang yang menggunakan Python tidak terlalu peduli tentang kecepatan dan tidak menulis kode Python yang optimal. Kami melihat ini khususnya dengan bahasa seperti Haskell. Jika Anda berusaha sangat keras , Anda dapat menulis Haskell yang berkinerja setara dengan C. Tetapi kebanyakan orang tidak membutuhkan kinerja itu, jadi kami memiliki banyak perbandingan yang salah.
Kadang-kadang, itu tidak aman, bukan abstraksi, yang membuat C cepat. Kurangnya batasan array dan null-pointer menghemat waktu, dan telah menjadi penyebab banyak celah keamanan selama bertahun-tahun.
Bahasa tidak cepat, implementasinya cepat. Banyak bahasa abstrak mulai lambat, karena kecepatan bukanlah tujuan mereka, tetapi semakin cepat karena semakin banyak optimasi yang ditambahkan.
Abstraksi vs pengorbanan kecepatan mungkin tidak akurat. Saya akan menyarankan perbandingan yang lebih baik:
Kesederhanaan, kecepatan, abstraksi: Pilih dua.
Jika kita menjalankan algoritme identik dalam berbagai bahasa, pertanyaan tentang kecepatan muncul pada masalah "Berapa banyak hal yang perlu kita lakukan saat runtime untuk membuat ini berfungsi?"
Dalam bahasa yang sangat abstrak yang sederhana , seperti Python atau JavaScript, karena kita tidak tahu tentang representasi data sampai runtime, pada akhirnya ada banyak penunjuk referensi dan pemeriksaan dinamis yang terjadi saat runtime, yang lambat.
Demikian juga, ada banyak pemeriksaan yang perlu dilakukan untuk memastikan Anda tidak merusak komputer Anda. Saat Anda memanggil fungsi dengan python, ia perlu memastikan bahwa objek yang Anda panggil sebenarnya adalah fungsi, karena jika tidak, Anda bisa menjalankan kode acak yang melakukan hal-hal buruk.
Akhirnya, sebagian besar bahasa abstrak memiliki overhead dari pengumpulan sampah. Sebagian besar pemrogram sepakat bahwa harus melacak masa pakai memori yang dialokasikan secara dinamis adalah hal yang menyusahkan, dan mereka lebih suka meminta seorang pengumpul sampah untuk melakukannya pada saat dijalankan. Ini membutuhkan waktu, bahwa program C tidak perlu dihabiskan untuk GC.
Abstrak Tidak berarti lambat
Namun, ada bahasa yang abstrak dan cepat. Yang paling dominan di era ini adalah Rust. Dengan memperkenalkan peminjam pinjaman dan sistem tipe yang canggih, memungkinkan kode abstrak, dan menggunakan informasi waktu kompilasi untuk mengurangi jumlah pekerjaan yang perlu kita lakukan saat runtime (yaitu pengumpulan sampah).
Setiap bahasa yang diketik secara statis menghemat waktu kita dengan mengurangi jumlah pemeriksaan runtime, dan memperkenalkan kompleksitas dengan mengharuskan kita untuk menyenangkan juru ketik pada saat kompilasi. Demikian pula, jika kita memiliki bahasa yang mengkodekan apakah suatu nilai dapat null ke dalam sistem tipenya, kita dapat menghemat waktu dengan mengkompilasi waktu pemeriksaan null-pointer.
sumber
berikut adalah beberapa ide kunci tentang ini.
perbandingan / studi kasus yang mudah untuk membuat abstraksi vs kecepatan adalah java vs c ++. java dirancang untuk mengabstraksi beberapa aspek c ++ tingkat rendah seperti manajemen memori. pada hari-hari awal (sekitar penemuan bahasa pertengahan 1990-an), deteksi sampah java tidak terlalu cepat, tetapi setelah beberapa dekade penelitian, pengumpul sampah sangat finetuned / cepat / dioptimalkan, sehingga pengumpul sampah sebagian besar dihilangkan sebagai menguras kinerja di java. misalnya, lihat bahkan tajuk utama 1998 ini: Tes kinerja menunjukkan Java secepat C ++ / javaworld
bahasa pemrograman dan evolusinya yang panjang memiliki "struktur piramidal / hierarkis" yang melekat sebagai semacam pola desain transendan. di bagian atas piramida adalah sesuatu yang mengontrol bagian bawah piramida lainnya. dengan kata lain, blok bangunan dibuat dari blok bangunan. ini dapat dilihat dalam struktur API juga. dalam pengertian ini, abstraksi yang lebih besar selalu mengarah pada beberapa komponen baru di puncak piramida yang mengendalikan komponen-komponen lain. jadi dalam arti itu tidak terlalu banyak sehingga semua bahasa sejajar satu sama lain, tetapi bahasa memanggil rutinitas dalam bahasa lain. mis. banyak bahasa scripting (python / ruby) sering memanggil pustaka C atau C ++, rutinitas numerik atau matriks adalah contoh khas dari ini. jadi ada bahasa tingkat yang lebih tinggi dan tingkat yang lebih rendah dan bahasa tingkat tinggi memanggil bahasa tingkat yang lebih rendah untuk berbicara. dalam hal ini mengukur kecepatan relatif tidak benar-benar sebanding.
orang mungkin mengatakan bahwa bahasa baru sedang diciptakan setiap saat untuk mencoba mengoptimalkan pengorbanan abstraksi / kecepatan, yaitu tujuan desain utamanya. mungkin ini bukan karena abstraksi yang lebih besar selalu mengorbankan kecepatan tetapi keseimbangan yang lebih baik selalu dicari dengan desain yang lebih baru. misalnya Google Go dalam banyak hal dioptimalkan secara khusus dengan mempertimbangkan tradeoff, untuk menjadi sekaligus berkinerja tinggi dan sekaligus. lihat misalnya Google Go: Mengapa bahasa pemrograman Google dapat menyaingi Java di enterprise / techworld
sumber
Cara saya suka berpikir tentang kinerja adalah "di mana karet memenuhi jalan". Komputer menjalankan instruksi, bukan abstraksi.
Yang ingin saya lihat adalah ini: apakah setiap instruksi yang dijalankan "menghasilkannya" dengan berkontribusi besar pada hasil akhir? Sebagai contoh yang terlalu sederhana, pertimbangkan mencari entri dalam tabel 1024 entri. Itu adalah masalah 10-bit karena program harus "belajar" 10 bit sebelum tahu jawabannya. Jika algoritma adalah pencarian biner, setiap iterasi menyumbang 1 bit informasi, karena itu menyusutkan ketidakpastian dengan faktor 2. Jadi dibutuhkan 10 iterasi, satu untuk setiap bit.
Pencarian linear, di sisi lain, pada awalnya sangat tidak efisien karena iterasi pertama mengecilkan ketidakpastian dengan faktor yang sangat kecil. Jadi mereka tidak banyak belajar untuk upaya yang dikeluarkan.
OK, jadi jika kompiler dapat memungkinkan pengguna untuk membungkus instruksi yang baik dengan cara yang dianggap "abstrak", itu bagus.
sumber
Pada dasarnya, abstraksi mengurangi komunikasi informasi, baik kepada pemrogram dan lapisan bawah sistem (kompiler, perpustakaan, dan sistem runtime). Dalam mendukung abstraksi, ini umumnya memungkinkan lapisan bawah untuk mengasumsikan bahwa programmer tidak peduli dengan perilaku yang tidak ditentukan, memberikan fleksibilitas yang lebih besar dalam memasok perilaku yang ditentukan.
Contoh manfaat potensial dari aspek "tidak peduli" ini adalah tata letak data. Dalam C (abstraksi rendah), kompiler lebih dibatasi dalam optimasi tata letak data. Bahkan jika kompiler dapat melihat (misalnya, melalui informasi profil) bahwa optimisasi penghindaran panas / dingin atau pembagian-salah akan bermanfaat, pada umumnya dicegah untuk menerapkannya. (Ada beberapa kebebasan dalam menentukan "seolah-olah", yaitu, memperlakukan spesifikasi secara lebih abstrak, tetapi menurunkan semua efek samping potensial menambah beban pada kompiler.)
Spesifikasi yang lebih abstrak juga lebih kuat terhadap perubahan pengorbanan dan penggunaan. Lapisan bawah kurang terkendala dalam mengoptimalkan program untuk karakteristik sistem baru atau penggunaan baru. Spesifikasi yang lebih konkrit harus ditulis ulang oleh seorang programmer atau upaya tambahan harus dilakukan oleh lapisan bawah untuk menjamin perilaku "seolah-olah".
Aspek yang menghambat kinerja dari abstraksi penyembunyian informasi adalah "tidak dapat mengekspresikan", yang biasanya ditangani oleh lapisan bawah sebagai "tidak tahu." Ini berarti bahwa lapisan bawah harus melihat informasi yang berguna untuk optimisasi dari cara lain seperti penggunaan umum, penggunaan yang ditargetkan, atau informasi profil tertentu.
Dampak dari menyembunyikan informasi juga berfungsi ke arah yang berbeda. Programer dapat lebih produktif dengan tidak harus mempertimbangkan dan menentukan setiap detail, tetapi programer mungkin memiliki lebih sedikit informasi tentang dampak dari pilihan desain level yang lebih tinggi.
Di sisi lain, ketika kode lebih spesifik (kurang abstrak), lapisan bawah sistem dapat lebih mudah melakukan apa yang diperintahkan kepada mereka karena mereka diminta melakukannya. Jika kode ditulis dengan baik untuk penggunaan yang ditargetkan, maka akan cocok dengan penggunaan yang ditargetkan. Bahasa yang kurang abstrak (atau paradigma pemrograman) memungkinkan programmer untuk mengoptimalkan implementasi dengan desain terperinci dan dengan menggunakan informasi yang tidak mudah dikomunikasikan dalam bahasa yang diberikan kepada lapisan bawah.
Seperti yang telah dicatat, bahasa yang kurang abstrak (atau teknik pemrograman) menarik ketika keterampilan dan upaya programmer tambahan dapat menghasilkan hasil yang bermanfaat. Ketika lebih banyak upaya dan keterampilan programmer diterapkan, hasilnya biasanya akan lebih baik. Selain itu, sistem bahasa yang kurang digunakan untuk aplikasi yang kritis terhadap kinerja (alih-alih menekankan upaya pengembangan atau keandalan - pemeriksaan batas dan pengumpulan sampah tidak hanya tentang produktivitas programmer tetapi tentang kebenaran, abstraksi yang mengurangi beban mental pada programmer dapat meningkatkan keandalan) akan memiliki lebih sedikit tekanan untuk meningkatkan kinerja.
Kekhususan juga bekerja bertentangan dengan prinsip jangan ulangi diri Anda karena pengoptimalan biasanya dimungkinkan dengan menyesuaikan kode untuk penggunaan tertentu. Ini jelas memiliki implikasi keandalan dan upaya pemrograman.
Abstraksi yang disediakan oleh bahasa juga dapat mencakup pekerjaan yang tidak diinginkan atau tidak perlu tanpa sarana untuk memilih abstraksi yang kurang berat. Sementara pekerjaan yang tidak perlu kadang-kadang dapat ditemukan dan dihapus oleh lapisan bawah (misalnya, batas pemeriksaan dapat diekstraksi dari badan loop dan sepenuhnya dihapus dalam beberapa kasus), menentukan bahwa itu adalah optimasi yang valid membutuhkan lebih banyak "keterampilan dan usaha" oleh kompiler.
Usia dan popularitas bahasa juga merupakan faktor penting baik dalam ketersediaan programmer yang terampil dan kualitas lapisan bawah sistem (termasuk perpustakaan yang matang dan contoh kode).
Faktor lain yang membingungkan dalam perbandingan semacam itu adalah perbedaan yang agak ortogonal antara kompilasi sebelumnya dan kompilasi just-in-time. Sementara kompilasi just-in-time dapat lebih mudah mengeksploitasi informasi profil (tidak bergantung pada programmer untuk menyediakan menjalankan profil) dan optimasi sistem khusus (kompilasi sebelumnya mungkin menargetkan kompatibilitas yang lebih luas), overhead optimasi agresif diperhitungkan sebagai bagian dari kinerja runtime. Hasil JIT dapat di-cache, mengurangi overhead untuk kode yang umum digunakan. (Alternatif optimalisasi biner dapat memberikan beberapa keuntungan dari kompilasi JIT, tetapi format distribusi biner tradisional menjatuhkan sebagian besar informasi kode sumber yang berpotensi memaksa sistem untuk mencoba melihat maksud dari implementasi tertentu.)
(Bahasa abstraksi yang lebih rendah, karena penekanannya pada kontrol programmer, mendukung penggunaan kompilasi di muka. Kompilasi install-waktu mungkin ditoleransi, meskipun pemilihan implementasi tautan-waktu akan memberikan kontrol programmer yang lebih besar. Kompilasi JIT mengorbankan kontrol yang signifikan. )
Ada juga masalah metodologi pembandingan. Upaya / keterampilan yang sama secara efektif tidak mungkin dibangun, tetapi bahkan jika itu dapat dicapai, tujuan bahasa akan membiaskan hasilnya. Jika waktu pemrograman maksimum yang rendah diperlukan, sebuah program untuk bahasa yang kurang abstrak mungkin gagal bahkan ditulis sepenuhnya dibandingkan dengan ungkapan idiomatik sederhana dalam bahasa yang lebih abstrak. Jika waktu / upaya pemrograman maksimum tinggi diizinkan, bahasa abstraksi yang lebih rendah akan memiliki keuntungan. Benchmark yang menyajikan hasil upaya terbaik secara alami akan bias mendukung bahasa yang kurang abstrak.
Terkadang mungkin untuk memprogram dengan cara yang kurang idiomatis dalam bahasa untuk mendapatkan keuntungan dari paradigma pemrograman lain, tetapi bahkan ketika kekuatan ekspresif tersedia, pengorbanan untuk melakukan hal tersebut mungkin tidak menguntungkan.
sumber