RAII, Fantasi Rust/Linux
(kristoff.it)Ringkasan
Ini adalah tulisan yang saya buat sambil mengikuti perselisihan antara pengembang Rust dan pengembang Linux lama. Banyak pengembang tentu bisa memiliki gaya pemrograman yang berbeda-beda, tetapi proyek Linux sudah pernah menyingkirkan C++ untuk menghindari gaya dan struktur kodenya (RAII).
Cara kerja kode yang disebutkan Asahi Lina terlalu lambat saat program tersebut dihentikan, dan bertentangan dengan pemrosesan batch, yaitu pendekatan paling mendasar dalam membuat perangkat lunak yang berorientasi kinerja. Misalnya, melakukan pemrosesan batch dengan memakai arena memori dapat menyelaraskan banyak masa hidup menjadi satu, sehingga RAII tidak diperlukan.
Berikut saya sajikan materi yang mendukung argumen saya. Semua materi ini menjelaskan mengapa pemrosesan batch itu baik:
- Casey Muratori | Smart-Pointers, RAII, ZII? Becoming an N+2 programmer
- CppCon 2014: Mike Acton "Data-Oriented Design and C++"
- Modern Systems Programming: Rust and Zig - Aleksey Kladov
Karena itu, saya pikir Linux seumur hidup tidak boleh menerima RAII.
Alasan saya membawa tulisan ini adalah sebagai berikut. Saya beberapa kali melihat pengembang Rust di Korea sangat marah setelah membaca tulisan itu, jadi saya penasaran apa pendapat orang-orang di sini. Bagaimana menurut kalian?
11 komentar
Menurut saya, saya agak bisa memahami elitisme dari sebagian pengembang tertentu. Dari sudut pandang "rekayasa" perangkat lunak, terutama pada Linux, sulit menemukan "perangkat lunak" lain yang di ekosistem open source saat ini telah bekerja sama luas bahkan dengan kubu closed source dan membantu kemajuan filosofi open source seperti ini; mungkin karena itulah mereka mempertahankan sikap konservatif yang bahkan terlihat eksklusif dan mirip Luddites, karena khawatir para programmer yang belum teruji akan berdatangan bak banjir dengan mengusung Rust, lalu menempelkan kode di luar kendali para inti pemelihara proyek yang ada, secara serampangan menambah utang teknis dan memperpendek siklus hidup Linux?
Menarik juga bahwa demi open source tetap lama menjadi open source, orang justru mengambil sikap yang tidak terlalu "terbuka".
Saya juga cukup sering menggunakan dan merekomendasikan RAII atau bentuk pengelolaan resource yang serupa. Karena bahkan tanpa benar-benar tahu apa itu RAII dan memakainya secara asal, tetap bisa menghasilkan kode yang “setidaknya aman”.
Namun, jika digunakan tanpa benar-benar memahaminya, sangat mudah menghasilkan banyak kode yang tidak efisien, misalnya sesuatu yang sebenarnya cukup membuka file sekali saja malah dibuka dan ditutup puluhan kali. Saya rasa jika developer terus-menerus memberi perhatian pada performa dan budaya seperti itu menjadi prasyarat di tim pengembang, RAII pun bisa memberikan performa pada tingkat yang cukup memadai.
freesetiap kali masing-masing objek dihapusfree, lalu menjalankannya secara bulk?Di Linux, apakah ada fitur? semacam API? yang membuat cara 2 berjalan lebih cepat daripada cara 1?
Saya selama ini tentu saja hidup dengan cara 1, jadi agak sulit memahaminya.
Saya tidak ingin kembali lagi ke pengalaman pengembangan di mana setelah selesai mengembangkan, saya harus mencari kebocoran memori dengan valgrind.
Saya tidak tahu persis, tetapi jika memilih untuk tidak menggunakan RAII, tampaknya maksudnya adalah meningkatkan kinerja
closedengan memanfaatkan kebocoran memori yang disengaja; saya tidak yakin apakah ini memang arah yang tepat.Bagaimanapun, pengembang yang pandai mengelola memori secara manual pada akhirnya juga akan mampu menggunakan RAII dengan baik, dan pengembang yang tidak bisa mengembangkan tanpa RAII kemungkinan juga tidak akan mampu mengelola memori secara manual, jadi rasanya tidak ada alasan untuk tidak menggunakan RAII.
Saya penasaran seberapa banyak waktu yang dihabiskan oleh
free, jadi meskipun sangat berbeda dari workload nyata, saya menulis kode sederhana untuk mengujinya. (Menggunakan build rilis Rust, denganstd::alloc::allocdanstd::fs::File.)Saya mengalokasikan 10.000.000 blok memori dengan ukuran beragam, total sekitar 2,5GB, lalu hanya mengukur waktu untuk membebaskannya, dan hasilnya 1,87 detik. Artinya sekitar 187ns per blok.
Sebaliknya, untuk file, saya hanya membuka sekitar 10.000 handle lalu mengukur waktu yang dibutuhkan untuk menutupnya, dan hasilnya sekitar 9 detik. Berarti sekitar 900us per file.
(PC Windows ini tampaknya sangat lambat untuk pekerjaan file, mungkin karena antivirus. Di laptop Windows lain masing-masing butuh 400ns/200us, dan di PC Linux lain 50ns/600ns.)
Sebagai alternatif RAII, sering disebut pemrosesan bulk atau membiarkan resource bocor dengan mengandalkan OS saat proses berakhir. Untuk memori, ini tampaknya cukup mudah dilakukan.
Namun untuk resource seperti file atau socket, saya belum pernah melihat API pemulihan bulk, dan jika resource dibiarkan bocor, mungkin waktu di kode user akan berkurang, tetapi sebanyak itu pula waktu yang dibutuhkan kernel untuk mengakhiri proses akan bertambah, jadi keuntungan performanya tidak terlalu besar.
RAII untuk memori juga relatif tidak terlalu lambat, bukan teknologi yang membuat penggunaan arena menjadi mustahil, dan juga tidak menghalangi kebocoran yang disengaja bila diperlukan, jadi rasanya sulit menjadikannya alasan untuk menghindari RAII.
Dan untuk RAII file yang lebih lambat, ketika tidak ada cara untuk memprosesnya secara bulk dan tidak ada cara untuk menghindari biayanya, saya jadi penasaran seberapa jauh alternatif RAII itu benar-benar lebih baik.
Sedikit di luar topik, tetapi saya mendapat kesan bahwa bantahan terkait RAII dan lifetime sering dibahas hanya terbatas pada resource memori yang diwakili oleh malloc/free.
RAII dan lifetime berguna secara luas bukan hanya untuk alokasi memori, tetapi juga untuk resource OS seperti file, socket, dan lock, serta object pool, connection pool, dan model resource lain pada umumnya yang memiliki proses akuisisi dan pengembalian, serta memerlukan kontrol akses eksklusif selama resource sedang dipegang.
Resource-resource ini juga berbagi struktur yang sama dengan malloc/free, sehingga berbagi pula masalah dengan struktur yang sama seperti kebocoran, use after free, dan double free,
dan justru karena berbagi struktur yang sama, saya rasa perlu lebih disorot bahwa RAII dan lifetime tidak hanya menyelesaikan masalah memori, tetapi juga sekaligus masalah pada resource-resource semacam ini.
Sebagai contoh, di Rust, use after close dan double close pada file handle juga dicegah pada waktu kompilasi:
https://play.rust-lang.org/?version=stable&mode=debug&edition=…
Bahasa-bahasa GC utama memang mengelola memori dengan GC, tetapi pada akhirnya untuk resource yang pengelolaannya harus deterministik seperti file handle dan socket,
mereka tetap memperkenalkan struktur seperti RAII (seperti
try-with-resourcesdi Java,usingdi C#,withdi Python) atau struktur serupa (sepertideferdi Go),sehingga pada akhirnya satu bahasa memiliki beberapa mode pengelolaan resource. Dibandingkan itu, menurut saya pendekatan ini mungkin sedikit lebih baik.
Jika yang Anda maksud adalah arena, tentu saja Rust juga memiliki arena, dan dengan mengaitkan arena tersebut melalui lifetime, Rust juga memungkinkan pelarangan akses ke elemen arena setelah arena itu "dibebaskan sekaligus". Silakan merujuk ke https://crates.io/keywords/arena .
Saya berharap akan ada lebih banyak bahasa yang muncul bahkan setelah Zig atau Rust. Tapi sampai sekarang, saya belum pernah melihat bahasa yang sama tepatnya dengan Rust. Justru saya merasa pengetahuan antar developer yang muncul dari diskusi di antara bahasa-bahasa seperti ini lebih bermanfaat. Haha..
Saya juga seorang pengembang yang cukup mengandalkan Rust sebagai bahasa utama, dan saya sendiri tidak sampai marah, tetapi rasanya agak mempertanyakan apakah yang dibawa bukan contoh yang terlalu ekstrem (misalnya, soal "program itu terlalu lambat saat ditutup"; bahkan dalam video yang ditautkan pun yang diangkat adalah kasus yang tidak berkaitan langsung dengan contoh di proyek Rust, melainkan kasus saat menutup Visual Studio yang memakan waktu terlalu lama karena destructor dari tiap komponen individual dipanggil satu per satu).
Jika dari sisi performa dibutuhkan agar clean-up dari banyak komponen diproses sekaligus, sepertinya bisa dipilih pendekatan untuk tidak mengimplementasikan
Droppada tiap komponen individual, melainkan mengimplementasikanDroppada tipe yang memegang masa hidup komponen-komponen tersebut agar clean-up dilakukan sekaligus. Akan lebih baik lagi jika ditambahkan pengaman sehingga komponen tersebut hanya bisa dibuat melalui API dari tipe itu.Tentu saja, kekhawatiran penulis artikel di atas tampaknya adalah bahwa jika praktik penggunaan RAII masuk ke codebase Linux, maka di dalam kompleksitas codebase yang sangat besar bisa terus menumpuk kode dengan kekhawatiran performa yang sangat implisit, sehingga dalam jangka panjang dapat terjadi hal yang mirip dengan Visual Studio, dan menurut saya itu memang titik kekhawatiran yang cukup masuk akal. Namun, seperti yang disebutkan di komentar lain, ada juga stabilitas yang diberikan RAII, jadi menurut saya pilihannya pada akhirnya adalah semacam trade-off.
Kedua pihak sama-sama mengatakan hal yang benar.
Kalau dianalogikan, karakter Azir di game online LoL punya citra sebagai champion tier tinggi dengan split push, kontrol area saat teamfight, dan value ultimate yang luar biasa bagus, tetapi itu hanya berlaku di pertandingan pro dengan tingkat kemahiran sangat tinggi; di level pemain biasa, dia terlalu lemah saat laning dan juga lemah secara stat, jadi pada dasarnya cuma champion tier terbawah.
Dari sudut pandang orang-orang seperti Asahi Lina yang punya pengetahuan pemrograman dan sistem operasi di 10% teratas atau lebih, alternatif selain RAII tentu saja lebih baik, tetapi di area yang ditangani oleh 90% sisanya, menurut saya tidak ada yang mengalahkan RAII atau Rust.
Namun, karena salah satu alasan besar mengapa stabilitas/keamanan memori harus dijamin adalah masalah keamanan... menurut saya tradeoff tidak bisa dihindari.
Tanpa RAII, rasanya developer yang pengalamannya relatif masih kurang akan cenderung menghasilkan banyak bug.
Kalau bukan di level OS, setidaknya di level aplikasi...