Garbage Collection untuk Programmer Sistem (2023)
(bitbashing.io)- Bahkan dalam kode yang mengutamakan performa dan concurrency seperti kernel dan driver, pembebasan memori tidak selesai hanya dengan pemanggilan
free()sederhana; teknik berbagi tanpa lock bisa saja diperlukan - RCU (Read, Copy, Update) adalah metode yang menyalin data yang sering dibaca dan jarang berubah, lalu mengganti pointer secara atomik sehingga jalur baca tidak terblokir
- Jika versi lama langsung di-
delete, use-after-free dapat terjadi pada thread yang masih membacanya; karena itu RCU melacak bagian baca dan menunda pembebasan sampai saat yang aman - RCU digunakan puluhan ribu kali di Linux, juga ada di library C++ Folly dan
crossbeam-epochmilik Rust; karena dibersihkan kemudian berdasarkan apakah masih digunakan, bentuknya mirip GC - Dikotomi bahwa manajemen memori manual selalu lebih cepat dan lebih mudah diprediksi itu lemah; karena
free(), reference counting, dan perilaku memori OS juga memiliki biaya serta ketidakpastian, GC modern juga bisa menjadi alat untuk pemrograman sistem
Mengapa kode kernel memakai teknik seperti RCU
- Sistem operasi termasuk jenis program yang dijalankan setiap hari dengan sensitivitas performa yang tinggi
- Jika OS menjadi lebih cepat, pengguna dapat melakukan lebih banyak komputasi, sehingga pengembang kernel dan driver mencurahkan banyak upaya untuk optimasi kode
- Sistem operasi harus menangani bukan hanya proses dan thread di user space, tetapi juga berbagai thread milik kernel itu sendiri serta handler interrupt perangkat keras
- Karena latensi yang meningkat akan mengambil waktu pengguna, dalam kode kernel muncul berbagai teknik untuk berbagi data antar-thread tanpa lock
Cara kerja dasar RCU
- RCU (Read, Copy, Update) adalah pendekatan yang cocok untuk data yang sangat sering dibaca tetapi jarang ditulis
- Contohnya adalah data seperti kumpulan perangkat USB yang saat ini terhubung: hampir tidak berubah, tetapi tetap bisa berubah
- Perubahan harus terjadi secara atomik dan tidak boleh menghambat pembaca yang sedang membaca
- Penulis memperbarui state bersama dengan urutan berikut
- Membaca data lama dari pointer
- Menyalin data lama dan menerapkan perubahan yang diperlukan untuk membuat versi baru
- Memperbarui pointer secara atomik agar menunjuk ke versi baru
- Pembaca hanya membaca pointer bersama, sehingga jalur baca sederhana dan berjalan tanpa menunggu
- Cara ini mudah digunakan dan wait-free, tetapi jika versi lama tidak dibersihkan, kebocoran memori akan terjadi
Versi lama yang tidak bisa langsung dibebaskan
- Jika pointer diganti ke versi baru lalu versi lama langsung di-
delete, muncul risiko use-after-free - Karena berjalan tanpa lock, penulis tidak dapat mengetahui apakah masih ada pembaca yang membaca versi lama
- Pembaca dapat menandai critical section sisi baca dengan
rcu_read_lock()danrcu_read_unlock()- Pembaca tetap tidak terblokir
- Penulis tidak menghapus data lama sampai pembaca tersebut keluar
rcu_synchronize()tidak perlu menunggu sampai semua pembaca hilang; cukup menunggu sampai pembaca lama yang mungkin melihat versi sebelumnya selesai- Pembaca yang melihat pointer baru akan memakai versi baru, sehingga tidak terkait dengan masa hidup versi lama
Pembebasan tertunda dan bentuk GC
- Meski penulis tidak menunggu di dalam fungsi pembaruan, kode tetap benar selama data lama pada akhirnya dibebaskan dengan aman
- Pendekatan seperti
rcu_defer(old)memungkinkanolddibebaskan kapan saja setelah pembaca saat ini keluar dari critical section - Bentuk di mana thread khusus secara berkala membersihkan versi lama yang tidak lagi direferensikan mirip dengan generational GC
- RCU bukan eksperimen pikiran, melainkan teknik yang benar-benar banyak digunakan
- Linux menggunakan RCU puluhan ribu kali
- RCU disediakan di Folly C++ library milik Facebook
- Di Rust, teknik ini digunakan dengan nama
crossbeam-epochdan menjadi fondasi library concurrency populer
- Hal yang lebih penting daripada perdebatan klasifikasi apakah RCU adalah “GC sungguhan” adalah bahwa strukturnya sama dengan GC: memori dibersihkan kemudian berdasarkan apakah sedang digunakan
Biaya tersembunyi dari pembebasan manual
- Anggapan umum bahwa GC secara inheren kurang efisien dibanding manajemen memori manual menjadi lemah jika melihat detail implementasinya
-
free()tidak gratis- Allocator memori serbaguna harus mengelola state global internal seperti page yang diterima dari kernel, pembagian bucket berdasarkan ukuran, dan bucket yang sedang digunakan
- Contention dapat terjadi ketika beberapa thread berusaha mengunci state allocator
- Meski memakai pool thread-local seperti jemalloc, tetap dibutuhkan kode tambahan untuk menyinkronkannya
-
RAII dan lifetime juga tidak menghilangkan biaya allocator
- Lifetime di Rust atau RAII di C++ membantu otomasi pembebasan memori dan correctness, tetapi tidak menghilangkan kompleksitas struktur internal allocator
- Dalam banyak skenario, kita harus kembali memakai
shared_ptratauArc - Metadata tambahan berupa reference count diperlukan, dan nilai ini dapat berpindah-pindah di antara core dan cache sehingga menimbulkan biaya
- Siklus dalam grafik liveness juga dapat menyebabkan kebocoran
-
Ada juga optimasi yang dapat diberikan GC
- Moving generational GC secara berkala memadatkan kembali heap
- Alokasi menjadi mirip dengan menaikkan pointer, sehingga dapat menghasilkan throughput tinggi
- Locality dari alokasi berurutan membaik, yang juga membantu performa cache
Ilusi tentang kontrol atas manajemen memori
- Banyak pengembang yang menolak GC sedang membuat sistem soft real-time
- Mereka menginginkan operasi secepat mungkin, seperti FPS dalam video game atau performa kompresi codec streaming
- Namun ini bukan kebutuhan latensi keras di mana sistem rusak atau orang meninggal jika sesekali memakan waktu 1 milidetik lebih lama
- Keyakinan bahwa programmer dapat menentukan kapan manajemen memori terjadi tidaklah sederhana
- Sistem operasi mengabstraksikan interaksi dengan perangkat keras
- Secara default, Linux hampir tidak melakukan apa pun saat ada permintaan memori, dan bisa menyerahkan memori saat benar-benar hendak digunakan
- Jika
madvise(), memory-mapped I/O, dan cache sistem file bercampur, tidak ada jawaban sederhana untuk “apa yang dialokasikan kapan” - Pada hari yang buruk, akses pointer sederhana pun bisa berujung pada disk I/O
- Keyakinan bahwa programmer selalu tahu kapan waktu yang baik untuk berhenti demi manajemen memori juga terbatas
- Ada kasus yang jelas seperti layar loading video game
- Dalam banyak software, satu-satunya jawaban adalah saat sedang tidak sibuk dengan pekerjaan yang lebih penting
- Kode individual yang memakai
shared_ptrdanArctidak bisa mengetahui sebelumnya apakah ia akan menjadi pemilik terakhir dan harus menanggung proses pembersihan
- Keyakinan bahwa pemanggilan
free()berarti memori langsung dikembalikan ke OS juga tidak selalu benar- Memori dialokasikan dari OS dalam satuan page
- Allocator sering kali menahan page dan menggunakannya kembali sampai program berakhir
- OS juga dapat mengambil kembali page melalui swap
Mengapa GC dapat dipandang sebagai alat pemrograman sistem
- Tidak semua software mendapat manfaat dari GC
- Namun bahkan menjelang 2024, diskusi tentang GC di kalangan programmer sistem mudah tertimbun oleh dikotomi yang keliru serta fear, uncertainty, and doubt
- Gagasan bahwa bahasa yang memakai GC “jelas” lebih lambat daripada bahasa dengan manajemen memori manual bukanlah fakta, melainkan lebih dekat ke ideologi
- Bahkan di tim yang membuat sistem yang menyangkut nyawa, ada contoh pemberian latensi sub-mikrodetik dengan bahasa GC yang melakukan alokasi di hampir setiap baris
- Jika sebagian sistem harus berjalan dalam
nsiklus clock, bagian spesifik itu dapat dipisahkan ke kode non-GC atau perangkat keras - GC bukan solusi serba bisa, tetapi merupakan salah satu alat dalam toolbox yang dapat digunakan tanpa rasa takut
1 komentar
Komentar Hacker News
Sebagai teknik garbage collection paralel modern yang menjanjikan, MPL atau MaPLe beserta Automatic Management of Parallelism barunya layak dilihat
Inti dari paper yang mendapat POPL 2024 distinguished paper award dan ACM SIGPLAN dissertation award 2023 ini ada dua: a) garbage collection paralel yang terbukti efisien berbasis disentanglement, b) kontrol granularitas otomatis yang terbukti efisien
[1] MaPLe (MPL): https://github.com/MPLLang/mpl
[2] Automatic Parallelism Management: https://dl.acm.org/doi/10.1145/3632880
Selain paper yang ditautkan, ada juga MLKit, yang merupakan salah satu pengguna awal sekaligus pelopor manajemen memori berbasis region
Misalnya, apakah pendekatan seperti ini bisa membuat garbage collection di Go jauh lebih cepat, atau justru akan terbentur masalah pada desain bahasa yang sudah ada?
Kasus penggunaan RCU memang meyakinkan, tetapi pengalaman dengan garbage collection dalam situasi lain tidak bagus
Tulisan ini lebih terasa seperti argumen bahwa solusi manajemen memori yang disesuaikan bisa memberikan performa terbaik, bukan bahwa masa hidup statis secara umum lebih baik daripada masa hidup dinamis
Saya tidak percaya bahwa
free()mengembalikan memori ke sistem operasi, melainkan ke allocator. Ini jauh lebih baik daripada mengembalikannya ke sistem operasi, dan system call itu lambat. Namun, seperti mimalloc, memori yang sudah dibebaskan juga bisa dibuat tidak langsung dipakai olehmallocberikutnya dan hanya tersedia secara berkalaSaat mengalokasikan 800 byte lalu langsung melakukan
freesebanyak 1 juta kali dan menghitung jumlah pointer unik, hasilnya glibc malloc 1, jemalloc 1, mimalloc 4, dan garbage collector Julia 6276762767 buah, sekitar 48 MiB, tidak terlalu buruk, tetapi tetap menggusur cache L3 di komputer saya. Jika memakai garbage collection, alokasi baru hampir pasti datang dari RAM, bukan cache, dan ini merusak performa kode yang banyak melakukan alokasi. Yang penting bukan hanya kecepatan manajemen memori itu sendiri, tetapi juga seberapa cepat kita bisa bekerja dengan memori yang diberikannya
Saya mengunggah benchmark yang menunjukkan hal ini di Julia: https://discourse.julialang.org/t/blog-post-rust-vs-julia-in...
malloc/free memberi peluang agar memori kerja nyata tetap panas di cache jika ukurannya cukup kecil. Allocator seperti mimalloc juga dirancang agar alokasi berurutan ditempatkan berdekatan seperti garbage collection kompaksi, dan 4 pointer unik mimalloc yang saya lihat berjarak 896 byte
Seandainya saya punya lebih banyak pengalaman dengan garbage collection kompaksi, mungkin pandangan saya tidak akan sesinis ini, tetapi menurut saya garbage collection adalah solusi yang jauh lebih kompleks untuk manajemen memori yang aman dibanding sesuatu seperti borrow checker Rust. Kompleksitas itu dialihkan ke pengembang compiler dan runtime, sehingga pengguna biasanya baik-baik saja, dan saat menulis kode yang tidak sensitif terhadap performa, ini adalah kompromi yang dapat diterima. RAII dengan masa hidup statis juga merupakan kompromi yang masuk akal untuk kode yang tidak memerlukan pendekatan yang lebih khusus, dan contoh dalam tulisan ini jelas merupakan kasus yang membutuhkan solusi khusus
Garbage collection kompaksi hampir selalu memanfaatkan cache lebih baik daripada malloc dalam program yang berjalan lama, karena fragmentasi heap membuang entri cache TLB dan ruang kosong di antara objek. Bump allocator pada garbage collection kompaksi memberikan pointer baru untuk setiap alokasi karena
freetidak merebut kembali memori, tetapi alokasi-alokasi itu berurutan, dan jika program terus mengonsumsi heap sambil hanya menyentuh objek paling baru, semuanya tetap berada di cache. Membenchmark efek turunan allocator dan garbage collector sangat sulit, dan hampir semua benchmark sintetis semacam ini saya pandang dengan skeptisYang dikhawatirkan orang adalah bahwa proses mencari tahu apakah memori sudah tidak digunakan lagi itu tidak efisien dan tidak deterministik dibanding langsung memberi tahu allocator bahwa penggunaan resource sudah selesai. Saya belum pernah melihat orang khawatir hanya karena pembebasan ditunda
Menelusuri seluruh kumpulan objek hidup memang jarang dilakukan, dan selama 30 tahun algoritma garbage collection telah diperbaiki sampai hampir seperti punya kesadaran, tetapi kalimat ini, sengaja atau tidak, mengabaikan titik yang sebenarnya dipermasalahkan orang. Saat terjadi masalah garbage collection di sebuah layanan, rasanya seperti harus memanggil dukun untuk menyetel ini-itu dan berharap roh-roh murka kembali ke dunia bayangan
Jika caranya adalah menandai sampah dan menerima notifikasi saat tidak lagi digunakan, seluruh proses itu hilang. Alokasi memori dalam garbage collection memang bisa sangat cepat, tetapi untuk perbandingan yang adil, biaya marking dan kompaksi juga harus diamortisasi dan ikut dihitung
Masalah besar lainnya adalah, untuk mendapatkan performa yang sama, garbage collection umumnya membutuhkan jauh lebih banyak memori daripada manajemen memori manual. Ia juga membutuhkan CPU ekstra untuk berulang kali memeriksa ulang apakah referensi masih ada, dan harus menerima penyalinan memori tambahan untuk kompaksi optimistis
Terakhir, tulisan itu mengkritik bahwa saat masa hidup tidak jelas, manajemen memori manual seperti Arc/Rc di Rust diperlukan, tetapi mengabaikan bahwa dalam bahasa dengan garbage collection pun finalizer tidak bisa dipercaya pasti dipanggil, sehingga pada praktiknya dibuat infrastruktur yang hampir sama untuk menutup resource eksternal
Perdebatan ini sudah cukup sering berulang selama 20–30 tahun terakhir, dan tulisan ini tampaknya tidak membawa hal baru selain menepis kekhawatiran sah terhadap garbage collection sebagai meme. Meme memang lucu, jadi tidak apa-apa, tetapi jawaban yang benar adalah tidak ada jawaban umum. Gunakan alat yang sesuai untuk memenuhi batasan desain sistem
Alokasi lain yang berumur lebih panjang, menurut definisinya, tidak mudah dikendalikan dari sudut pandang cache. Lokalitas adalah salah satu keunggulan besar garbage collection, dan satu-satunya masalah yang saya tahu adalah mark/sweep dengan stop-the-world. Saya tahu garbage collection modern memiliki thread latar belakang, tetapi setahu saya event stop-the-world tetap terjadi
free()mengembalikan memori ke allocator, tetapi menangani fragmentasi memori pada server yang berjalan lama sama sekali tidak menyenangkanTerutama fragmentasi internal pada halaman yang dikelola slab allocator; ini bukan masalah umum, tetapi sulit ditangani
Untungnya, tidak banyak ranah tempat garbage collection harus bersaing dengan contoh yang begitu nyaman direkayasa seperti ini
Kecuali untuk kasus khusus ketika semua memori dapat dengan mudah ditangani sebagai arena, tracing garbage collection yang baik sudah lama melampaui manajemen memori manual dalam hal throughput, dan belakangan dampak latensinya juga sudah cukup dapat diterima untuk sebagian besar aplikasi
ZGC di OpenJDK biasanya memiliki waktu jeda dalam kisaran dua hingga tiga digit mikrodetik, dan pada laju alokasi yang wajar, kasus terburuknya pun jarang melampaui 1 ms; ini berada di kisaran yang mirip dengan jeda yang dipicu sistem operasi
Trade-off yang benar-benar penting hanyalah penggunaan memori. Selain ceruk khusus—yakni ketika arena cocok untuk semuanya dan latensi terburuk berada di kisaran mikrodetik rendah—pertanyaan intinya hanya satu: apakah aplikasi saya berjalan di lingkungan dengan keterbatasan memori, atau apakah layak mengorbankan hal lain demi mengurangi penggunaan RAM
Dalam kode seperti itu, melacak masa hidup objek satu per satu itu berlebihan. Pada akhirnya, manajemen memori adalah soal masa hidup, dan jumlah masa hidup individual yang lebih sedikit selalu lebih baik daripada yang banyak. Sebab pekerjaan yang harus dilakukan jadi lebih sedikit, baik manual maupun otomatis
Tidak perlu memikirkan masa hidup objek itu sangat nyaman, dan karena itu bahasa dengan garbage collection sukses meskipun kompleksitas internal garbage collector yang baik cukup besar
Kita juga tetap terkena dampak tail latency yang sulit diprediksi dan berbagai kondisi pengecualian
ZGC tampaknya memiliki jumlah jeda lebih sedikit daripada Shenandoah, sehingga sepertinya melakukan lebih banyak pekerjaan per jeda dan menunjukkan performa yang sedikit lebih baik
Masih perlu diuji di lingkungan produksi, tetapi sejauh ini, dengan ZGC—dan generational ZGC sejak Java 21—jeda garbage collection tampaknya secara umum sudah menjadi masalah yang terselesaikan
Seperti sudut pandang Jai, alokasi memori dapat dibagi menjadi empat kategori menurut frekuensinya: 1) yang hidup sangat singkat sehingga bisa ditempatkan di stack fungsi, 2) yang hidup singkat dan masa hidupnya terdefinisi jelas sehingga bisa ditempatkan di arena memori per frame/request, 3) yang hidup lama dan pemiliknya terdefinisi jelas sehingga bisa dikelola dengan pool khusus subsistem, 4) yang hidup lama dan pemiliknya tidak jelas sehingga membutuhkan manajemen memori dinamis
Untuk mengklaim bahwa tracing garbage collection secara umum mengungguli manajemen memori manual, pembandingnya seharusnya bukan sistem yang memanggil malloc/free di sana-sini, melainkan sistem yang ditulis dengan perspektif seperti ini. Perbandingan dengan praktik C++/Rust modern mungkin akan lebih adil
Saya setuju bahwa pada sebagian besar sistem, mengandalkan tracing garbage collection kemungkinan jauh lebih praktis, tetapi itu klaim yang sama sekali berbeda
Tulisan ini memotivasi RCU, lalu berbalik arah dan mulai membela garbage collection serbaguna secara umum
Bukan sampai seperti kuda Troya, tetapi rasanya seperti peralihan yang cukup mendadak
Objek berada dalam salah satu dari tiga keadaan dan berpindah secepat mungkin: active, obsolete but alive for old readers, deallocated
Tergantung cara penulisan kodenya, objek “obsolete-but-alive” mungkin juga dapat digunakan ulang dengan aman untuk alokasi “new”, tetapi performanya belum saya analisis sepenuhnya
Seperti yang sering terjadi dalam diskusi garbage collection, kapan harus “mundur” ke
shared_ptr/Arcsangat tidak jelas. Dalam praktiknya, menghindari reference count—yakni membuktikan bahwa kepemilikan sudah ada, atau menghindari indireksi sama sekali—adalah inti dari sistem serius berbasis reference counting. Tidak melakukan apa-apa tentu lebih baik daripada “melakukan sesuatu suatu saat nanti” ala garbage collectionDalam perangkat lunak yang saya pakai, ada dua kasus. (1) Hot path yang selalu memakai allocator kustom dan menghindari alokasi, (2) semua yang lain
Pada (1), entah memakai garbage collection atau tidak, tidak ada bedanya, dan saya akan mencari jalan keluar. Pada (2), garbage collection benar-benar nyaman dan benar
Pekerjaan yang dilakukan Java dengan garbage collector modern memang mengesankan, tetapi bahkan mereka pun secara tidak langsung mengakui lewat Valhalla bahwa ada tempat untuk kode tanpa alokasi/beralokasi rendah
Observasi bahwa sistem operasi modern untuk pengguna—yakni sistem operasi yang bukan RTOS khusus—memiliki garbage collection bawaan sedikit melenceng dari sini
Kita hanya tidak menyebutnya begitu, melainkan menyebutnya manajemen memori. Bahasa yang punya garbage collection bawaan disebut apa? Bahasa dengan manajemen memori
Ini sering terlihat pada program C lama yang berjalan “dari atas ke bawah”. Mengalokasikan memori, membersihkan resource sistem, tetapi tidak peduli pada
free. Saat program berakhir, sistem operasi akan mengambil kembali semua memori itu, jadi untuk apa repot-repot?Di sini ada peluang untuk membuat garbage collector di level sistem operasi yang tidak terlalu terisolasi dari program, atau sistem operasi yang menangani resource seperti garbage collector runtime bahasa. Namun, dalam bahasa dengan garbage collection biasanya garbage collector terjalin rumit dengan hampir setiap baris runtime, sehingga tidak praktis membuat hanya distribusi untuk satu sistem operasi menyerahkan kontrol itu ke sistem operasi
Meski begitu, ini tetap disayangkan. Ada banyak ruang untuk memperbaiki masalah kronis yang muncul karena pemisahan artifisial antara manajemen memori level program dan level sistem operasi
Satu-satunya alasan membebaskan memori adalah agar aplikasi yang berjalan lama dapat menggunakannya kembali untuk alokasi lain tanpa meminta memori baru lagi dari sistem operasi. Untuk tool command-line yang dijalankan sekali lalu selesai, biasanya ini tidak diperlukan
Jika sebuah proses dirancang untuk keluar setelah pekerjaannya selesai, sistem operasi bisa dipakai seperti garbage collector
Namun, tidak pernah ada cara bagi sistem operasi untuk mengetahui memori mana yang tidak lagi digunakan di dalam program yang masih berjalan. Mungkin pengecualiannya adalah sistem operasi riset yang esoterik. Jadi menurut saya ini bukan peluang yang terlewat, melainkan objek yang disebut “terlewat” itu tidak benar-benar ada dalam bentuk yang bermakna
Sebaliknya, gaya pemrograman yang memakai program sangat sederhana dan berumur pendek sepenuhnya sah. Tool CLI dan bahasa skrip yang menskripnya bekerja seperti itu, server web lama juga bekerja seperti itu melalui CGI dan semacamnya, dan sampai hari ini pun itu tetap pendekatan yang cukup masuk akal
(1) Peralihan dari RCU ke garbage collection pelacakan yang serbaguna terasa seperti bait-and-switch
(2) Manajemen memori manual bukan hanya soal pemanggilan malloc/free, tetapi juga soal layout. Misalnya pemisahan array struct, inlining, offset implisit, packing, dan sebagainya
Tampaknya yang dimaksud adalah “array of structs”; ini bisa dilakukan dengan array tuple dan, bergantung targetnya, akan diratakan/dinormalisasi secara alami. Artinya, pada target native, hasilnya menjadi array of structs
Layout yang presisi hingga level byte juga bisa didefinisikan[1], terutama dipakai untuk integrasi dengan software lain atau parsing format biner. Tipe data aljabar dapat di-unbox, dan sebentar lagi encoding presisi tipe data aljabar pun dapat dikendalikan
Virgil menggunakan garbage collection
[1] https://github.com/titzer/virgil/blob/master/doc/tutorial/La...
Memang jelas bahasa terkelola modern kekurangan kontrol layout memori, tetapi bahkan bahasa level rendah pun masih jauh dari sempurna, dan jelas ada cara untuk memengaruhinya
Satu hal yang terlewat dari tulisan ini adalah bahwa async/await sangat cocok dengan garbage collection
Saya tidak menyukai async/await karena alasan gaya pribadi yang agak khas, tetapi tidak akan saya bahas panjang
Saya sudah banyak memakainya di TypeScript/JavaScript dan juga pernah di Dart, dan di sana perilakunya sesuai harapan
Saya juga pernah memakainya di Rust, dan menurut saya itu bencana. Memaksakan jenis manajemen memori yang dibutuhkan untuk memakai async/await pada runtime multithread itu benar-benar neraka
https://doc.rust-lang.org/std/pin/index.html
Ada hal yang sering terlewat dalam banyak tulisan yang membela garbage collection, dan tulisan ini juga tampaknya demikian: memori hanyalah salah satu jenis resource
Khususnya dalam pemrograman sistem, kode yang benar juga harus mengelola resource eksternal seperti file handle, socket, dan sebagainya. Garbage collection hanya menyelesaikan bagian memori aplikasi, jadi sama sekali tidak membantu penanganan resource eksternal seperti ini. Malah bisa membuatnya jauh lebih rumit; cukup lihat saja apa yang diperlukan untuk mengimplementasikan
IDisposabledengan benar di .NET untuk kasus yang tidak sepelePendekatan seperti RAII atau reference counting, menurut pengalaman saya, membuat memori dan resource eksternal jauh lebih mudah ditangani secara terpadu, sehingga lebih mudah menulis kode yang benar dan menalarinya
Meski begitu, saya bukan berarti terang-terangan menentang garbage collection. Seperti hal lain, ini adalah alat yang punya kelebihan dan kekurangan. Pendekatan RCU “GC manual” yang disebut dalam tulisan tersebut menarik untuk pekerjaan tertentu
Bukan tanpa alasan sebagian besar model komputasi teoretis mengasumsikan memori tak terbatas. Pada sebagian software seperti kernel sistem operasi atau aplikasi hard real-time, kemampuan pemrosesan juga dialokasikan secara manual, tetapi hampir tidak ada bahasa yang menuntut kemampuan pemrosesan dialokasikan secara manual
Dengan alasan serupa, manajemen memori otomatis sangat berguna untuk mengabstraksikan komputasi. Ini mencegah detail memori sebuah subrutin bocor ke pemanggilnya; pada penggunaan CPU pun detail seperti ini jarang terlihat
Setiap komputasi yang tidak trivial mencakup sejumlah pemrosesan dan memori yang tidak konstan, tetapi I/O biasanya terjadi di tepi sistem. Manajemen I/O tentu juga sangat penting, tetapi dalam konsep komputasi dan sentralitas abstraksi komputasi, I/O tidak sefundamental pemrosesan dan memori
Misalnya, Rust pada akhirnya memiliki I/O safety, sehingga file handle seperti
OwnedFddi Unix atau handle sepertiOwnedHandledi Windows menjadi objek yang dimiliki, bukan integer seperti angka 4Di permukaan, ini tampak seperti soal menghindari kesalahan konyol seperti melakukan operasi aritmetika pada handle atau keliru memakai nilai cadangan sebagai sentinel, tetapi berkat model ownership, bahkan saat melakukan operasi rumit dengan handle, kepemilikan menjadi eksplisit dan tetap transparan bagi pemelihara berikutnya
Saya benar-benar merasakan besarnya nilai RAII
Ada bahasa yang punya RAII, ada yang menyediakan keyword, ada yang menyediakan manajemen seperti arena atau lambda dengan manajemen implisit. Ada bahasa yang sedikit dibantu type system, dan ada pula yang mencampur sedikit dari semua hal di atas
Selain itu, sebagaimana developer sistem harus mengandalkan static analyzer, static analyzer pada bahasa-bahasa seperti itu juga bisa memverifikasi hal-hal yang terlewat saat type system saja tidak cukup
Misalnya, pernyataan try-with-resources di Java menjamin resource dilepas dengan aman meskipun terjadi exception: https://docs.oracle.com/javase/tutorial/essential/exceptions...
Dengan building block dasar seperti ini pun, kita bisa membuat sistem yang cukup solid dan resource-safe