Kebocoran Fisik
Jenis bug yang ditangani oleh GC tampaknya (setidaknya bagi pengamat eksternal) hal-hal yang tidak dapat dilakukan oleh programmer yang mengerti bahasa, perpustakaan, konsep, idiom, dll. Tapi saya bisa saja salah: apakah penanganan memori manual pada hakekatnya rumit?
Berasal dari ujung C yang membuat manajemen memori sekitar sebagai manual dan diucapkan mungkin sehingga kami membandingkan ekstrem (C ++ sebagian besar mengotomatiskan manajemen memori tanpa GC), saya akan mengatakan "tidak benar-benar" dalam arti membandingkan dengan GC ketika datang ke kebocoran . Seorang pemula dan kadang-kadang bahkan seorang profesional mungkin lupa menulis free
untuk diberikan malloc
. Ini pasti terjadi.
Namun, ada alat seperti valgrind
deteksi kebocoran yang akan segera ditemukan, pada saat mengeksekusi kode, ketika / di mana kesalahan tersebut terjadi hingga ke baris kode yang tepat. Ketika itu diintegrasikan ke dalam CI, menjadi hampir tidak mungkin untuk menggabungkan kesalahan seperti itu, dan mudah untuk memperbaikinya. Jadi itu tidak pernah menjadi masalah besar dalam tim / proses apa pun dengan standar yang masuk akal.
Memang, mungkin ada beberapa kasus eksotis eksekusi yang terbang di bawah radar pengujian di mana free
gagal dipanggil, mungkin saat menghadapi kesalahan input eksternal yang tidak jelas seperti file korup di mana kasus mungkin sistem bocor 32 byte atau sesuatu. Saya pikir itu pasti dapat terjadi bahkan di bawah standar pengujian yang cukup bagus dan alat deteksi kebocoran, tetapi juga tidak terlalu kritis untuk membocorkan sedikit memori pada sesuatu yang hampir tidak pernah terjadi. Kita akan melihat masalah yang jauh lebih besar di mana kita dapat membocorkan sumber daya besar bahkan di jalur eksekusi umum di bawah dengan cara yang tidak dapat dicegah oleh GC.
Ini juga sulit tanpa sesuatu yang menyerupai pseudo-bentuk GC (penghitungan referensi, misalnya) ketika masa hidup suatu objek perlu diperpanjang untuk beberapa bentuk pemrosesan yang ditangguhkan / tidak sinkron, mungkin dengan utas lainnya.
Pointer Menggantung
Masalah sebenarnya dengan bentuk manajemen memori yang lebih manual tidak bocor bagi saya. Berapa banyak aplikasi asli yang ditulis dalam C atau C ++ yang kita ketahui benar-benar bocor? Apakah kernel Linux bocor? MySQL? CryEngine 3? Stasiun kerja dan synthesizer audio digital? Apakah Java VM bocor (ini diterapkan dalam kode asli)? Photoshop?
Jika ada, saya pikir ketika kita melihat-lihat, aplikasi yang paling sedikit cenderung yang ditulis menggunakan skema GC. Tetapi sebelum itu dianggap sebagai slam pada pengumpulan sampah, kode asli memiliki masalah signifikan yang sama sekali tidak terkait dengan kebocoran memori.
Masalah bagi saya selalu aman. Bahkan ketika kita free
mengingat melalui pointer, jika ada pointer lain ke sumber daya, mereka akan menjadi pointer (tidak valid) menggantung.
Ketika kami mencoba mengakses pointees dari pointer yang menggantung itu, kami akhirnya menemukan perilaku yang tidak terdefinisi, meskipun hampir selalu merupakan pelanggaran segfault / akses yang mengarah pada crash yang keras dan langsung.
Semua aplikasi asli yang saya sebutkan di atas berpotensi memiliki satu atau dua kasus tepi yang tidak jelas yang dapat menyebabkan crash terutama karena masalah ini, dan pasti ada bagian yang adil dari aplikasi jelek yang ditulis dalam kode asli yang sangat crash-heavy, dan seringkali sebagian besar karena masalah ini.
... dan itu karena manajemen sumber daya sulit terlepas dari apakah Anda menggunakan GC atau tidak. Perbedaan praktisnya seringkali bocor (GC) atau menabrak (tanpa GC) dalam menghadapi kesalahan yang mengarah pada kesalahan pengelolaan sumber daya.
Manajemen Sumber Daya: Pengumpulan Sampah
Manajemen sumber daya yang kompleks adalah proses manual yang sulit, apa pun yang terjadi. GC tidak dapat mengotomatiskan apa pun di sini.
Mari kita ambil contoh di mana kita memiliki objek ini, "Joe". Joe direferensikan oleh sejumlah organisasi yang menjadi anggotanya. Setiap bulan atau lebih mereka mengambil biaya keanggotaan dari kartu kreditnya.
Kami juga memiliki satu referensi untuk Joe untuk mengendalikan hidupnya. Katakanlah, sebagai programmer, kita tidak lagi membutuhkan Joe. Dia mulai mengganggu kita dan kita tidak lagi membutuhkan organisasi yang menjadi miliknya untuk membuang waktu berurusan dengannya. Jadi kami berusaha untuk menghapusnya dari muka bumi dengan menghapus referensi garis hidupnya.
... tapi tunggu, kami menggunakan pengumpulan sampah. Setiap referensi kuat untuk Joe akan membuatnya tetap ada. Jadi kami juga menghapus referensi kepadanya dari organisasi tempat dia berada (berhenti berlangganan).
... kecuali whoops, kami lupa membatalkan langganan majalahnya! Sekarang Joe tetap ada di memori, mengganggu kami dan menghabiskan sumber daya, dan perusahaan majalah juga akhirnya terus memproses keanggotaan Joe setiap bulan.
Ini adalah kesalahan utama yang dapat menyebabkan banyak program rumit yang ditulis menggunakan skema pengumpulan sampah bocor dan mulai menggunakan semakin banyak memori semakin lama berjalan, dan mungkin semakin banyak pemrosesan (berlangganan majalah berulang). Mereka lupa untuk menghapus satu atau lebih referensi itu, sehingga mustahil bagi pengumpul sampah untuk melakukan keajaiban sampai seluruh program ditutup.
Namun, program ini tidak macet. Sangat aman. Ini hanya akan terus memonopoli memori dan Joe masih akan berlama-lama. Untuk banyak aplikasi, perilaku bocor semacam ini di mana kita hanya membuang lebih banyak memori / pemrosesan pada masalah ini mungkin jauh lebih baik daripada hard crash, terutama mengingat berapa banyak memori dan daya pemrosesan yang dimiliki mesin kita saat ini.
Manajemen Sumber Daya: Manual
Sekarang mari kita pertimbangkan alternatif di mana kita menggunakan pointer ke Joe dan manajemen memori manual, seperti:
Tautan biru ini tidak mengatur masa pakai Joe. Jika kami ingin menghapusnya dari muka bumi, kami secara manual meminta untuk menghancurkannya, seperti:
Sekarang biasanya akan meninggalkan kita dengan pointer menggantung di semua tempat, jadi mari kita hapus pointer ke Joe.
... wah, kami membuat kesalahan yang sama lagi dan lupa untuk berhenti berlangganan majalah Joe!
Kecuali sekarang kita memiliki pointer menggantung. Ketika langganan majalah mencoba memproses biaya bulanan Joe, seluruh dunia akan meledak - biasanya kita mendapatkan hard crash langsung.
Kesalahan manajemen sumber daya dasar yang sama di mana pengembang lupa untuk menghapus secara manual semua petunjuk / referensi ke sumber daya dapat menyebabkan banyak crash pada aplikasi asli. Mereka tidak menyimpan memori lebih lama mereka berjalan biasanya karena mereka akan sering crash dalam kasus ini.
Dunia nyata
Sekarang contoh di atas menggunakan diagram yang sangat sederhana. Aplikasi dunia nyata mungkin memerlukan ribuan gambar dijahit bersama untuk menutupi grafik penuh, dengan ratusan jenis sumber daya yang berbeda disimpan dalam grafik adegan, sumber daya GPU yang terkait dengan beberapa dari mereka, akselerator terikat dengan yang lain, pengamat yang didistribusikan di ratusan plugin menonton sejumlah jenis entitas dalam adegan untuk perubahan, pengamat yang mengamati pengamat, audio yang disinkronkan dengan animasi, dll. Jadi, sepertinya mudah untuk menghindari kesalahan yang saya jelaskan di atas, tetapi umumnya tidak ada yang dekat dengan hal sederhana ini di dunia nyata basis kode produksi untuk aplikasi kompleks yang menjangkau jutaan baris kode.
Kemungkinan seseorang, suatu hari, akan salah mengelola sumber daya di suatu tempat dalam basis kode itu cenderung cukup tinggi, dan probabilitasnya sama dengan atau tanpa GC. Perbedaan utama adalah apa yang akan terjadi sebagai akibat dari kesalahan ini, yang juga mempengaruhi secara potensial mempengaruhi seberapa cepat kesalahan ini akan terlihat dan diperbaiki.
Kecelakaan vs. Kebocoran
Sekarang yang mana yang lebih buruk? Kecelakaan seketika, atau keheningan diam-diam bocor di mana Joe secara misterius hidup?
Sebagian besar mungkin menjawab yang terakhir, tetapi katakanlah perangkat lunak ini dirancang untuk dijalankan selama berjam-jam, mungkin berhari-hari, dan masing-masing Joe dan Jane yang kami tambahkan meningkatkan penggunaan memori perangkat lunak sebesar satu gigabyte. Ini bukan perangkat lunak mission-critical (crash tidak benar-benar membunuh pengguna), tetapi kritis-kinerja.
Dalam hal ini, crash keras yang segera muncul saat debugging, menunjukkan kesalahan yang Anda buat, mungkin sebenarnya lebih baik daripada hanya perangkat lunak bocor yang bahkan mungkin terbang di bawah radar prosedur pengujian Anda.
Di sisi lain, jika itu adalah perangkat lunak misi-kritis di mana kinerja bukan tujuan, hanya saja tidak menabrak dengan cara apa pun yang mungkin, maka bocor mungkin sebenarnya lebih disukai.
Referensi yang lemah
Ada semacam hibrida dari ide-ide ini yang tersedia dalam skema GC yang dikenal sebagai referensi lemah. Dengan referensi yang lemah, kita dapat memiliki semua organisasi ini sebagai referensi lemah Joe tetapi tidak mencegahnya dihapus ketika referensi kuat (pemilik / garis hidup Joe) hilang. Namun demikian, kita mendapat manfaat dari dapat mendeteksi ketika Joe tidak lagi melalui referensi yang lemah ini, memungkinkan kita untuk mendapatkan jenis kesalahan yang mudah direproduksi.
Sayangnya referensi yang lemah tidak digunakan sebanyak yang seharusnya digunakan, jadi sering kali banyak aplikasi GC kompleks rentan terhadap kebocoran bahkan jika mereka berpotensi jauh lebih tidak crash daripada aplikasi C kompleks, misalnya
Bagaimanapun, apakah GC membuat hidup Anda lebih mudah atau lebih sulit tergantung pada seberapa penting bagi perangkat lunak Anda untuk menghindari kebocoran, dan apakah itu berhubungan dengan manajemen sumber daya yang kompleks atau tidak.
Dalam kasus saya, saya bekerja di bidang kritis kinerja di mana sumber daya melakukan rentang ratusan megabyte hingga gigabytes, dan tidak melepaskan memori itu ketika pengguna meminta untuk membongkar karena kesalahan seperti di atas sebenarnya bisa lebih disukai daripada crash. Gangguan mudah dikenali dan direproduksi, membuatnya sering menjadi bug favorit programmer, bahkan jika itu adalah bug yang paling tidak disukai pengguna, dan banyak dari crash ini akan muncul dengan prosedur pengujian yang waras bahkan sebelum mereka mencapai pengguna.
Bagaimanapun, itulah perbedaan antara GC dan manajemen memori manual. Untuk menjawab pertanyaan langsung Anda, saya akan mengatakan manajemen memori manual sulit, tetapi tidak ada hubungannya dengan kebocoran, dan baik GC maupun bentuk manajemen memori manual masih sangat sulit ketika manajemen sumber daya tidak sepele. GC bisa dibilang memiliki perilaku yang lebih rumit di sini di mana program tersebut tampaknya berfungsi dengan baik tetapi memakan sumber daya yang semakin banyak. Bentuk manual kurang rumit, tetapi akan crash dan membakar waktu besar dengan kesalahan seperti yang ditunjukkan di atas.