Saat ini, banyak sekali bahasa yang dikumpulkan dari sampah. Bahkan tersedia untuk C ++ oleh pihak ketiga. Tetapi C ++ memiliki RAII dan pointer pintar. Jadi apa gunanya menggunakan pengumpulan sampah? Apakah itu melakukan sesuatu yang ekstra?
Dan dalam bahasa lain seperti C #, jika semua referensi diperlakukan sebagai smart pointer (menjauhkan RAII), dengan spesifikasi dan implementasi, apakah masih akan ada kebutuhan pemulung? Jika tidak, mengapa ini tidak terjadi?
garbage-collection
smart-pointer
Gulshan
sumber
sumber
Jawaban:
Saya berasumsi maksud Anda referensi dihitung pointer pintar dan saya akan mencatat bahwa mereka adalah bentuk (sampah) pengumpulan sampah jadi saya akan menjawab pertanyaan "apa keuntungan dari bentuk pengumpulan sampah lainnya dibandingkan referensi dihitung pointer pintar" sebagai gantinya.
Akurasi . Referensi menghitung sendiri kebocoran siklus sehingga referensi yang dihitung pointer pintar akan membocorkan memori secara umum kecuali jika teknik lain ditambahkan untuk menangkap siklus. Setelah teknik-teknik itu ditambahkan, manfaat penghitungan referensi dari kesederhanaan telah sirna. Juga, perhatikan bahwa penghitungan referensi berbasis ruang lingkup dan penelusuran GCs mengumpulkan nilai pada waktu yang berbeda, terkadang penghitungan referensi mengumpulkan lebih awal dan kadang-kadang pelacakan GC dikumpulkan lebih awal.
Throughput . Pointer pintar adalah salah satu bentuk pengumpulan sampah yang paling tidak efisien, khususnya dalam konteks aplikasi multi-utas ketika jumlah referensi ditabrak secara atom. Ada teknik penghitungan referensi canggih yang dirancang untuk meringankan ini tetapi melacak GC masih merupakan algoritma pilihan di lingkungan produksi.
Latensi . Implementasi smart pointer yang khas memungkinkan destructors untuk longsoran, menghasilkan waktu jeda yang tidak terbatas. Bentuk pengumpulan sampah lainnya jauh lebih bertahap dan bahkan dapat waktu nyata, misalnya treadmill milik Baker.
sumber
Karena tidak ada yang melihatnya dari sudut ini, saya akan ulangi pertanyaan Anda: mengapa memasukkan sesuatu ke dalam bahasa jika Anda bisa melakukannya di perpustakaan? Mengabaikan implementasi spesifik dan detail sintaksis, petunjuk GC / smart pada dasarnya adalah kasus khusus dari pertanyaan itu. Mengapa mendefinisikan pengumpul sampah dalam bahasa itu sendiri jika Anda dapat menerapkannya di perpustakaan?
Ada beberapa jawaban untuk pertanyaan itu. Yang terpenting pertama:
Anda memastikan bahwa semua kode dapat menggunakannya untuk beroperasi. Hal ini, saya pikir, yang alasan besar mengapa menggunakan kembali kode dan berbagi kode tidak benar-benar lepas landas sampai Java / C # / Python / Ruby. Perpustakaan perlu berkomunikasi, dan satu-satunya bahasa bersama yang dapat diandalkan yang mereka miliki adalah apa yang ada dalam spesifikasi bahasa itu sendiri (dan, pada tingkat tertentu, perpustakaan standarnya). Jika Anda pernah mencoba menggunakan kembali pustaka di C ++, kemungkinan Anda mengalami rasa sakit luar biasa yang tidak disebabkan oleh semantik memori standar. Saya ingin meneruskan sebuah struct ke beberapa lib. Apakah saya lulus referensi? Pointer?
scoped_ptr
?smart_ptr
? Apakah saya lulus kepemilikan, atau tidak? Apakah ada cara untuk menunjukkan itu? Bagaimana jika lib perlu dialokasikan? Apakah saya harus memberinya pengalokasi? Dengan tidak menjadikan manajemen memori bagian dari bahasa, C ++ memaksa setiap pasangan perpustakaan untuk menegosiasikan strategi spesifik mereka di sini, dan sangat sulit untuk membuat mereka semua setuju. GC menjadikan itu sepenuhnya tidak menjadi masalah.Anda dapat mendesain sintaks di sekitarnya. Karena C ++ tidak merangkum manajemen memori itu sendiri, itu harus menyediakan berbagai kait sintaksis untuk membiarkan kode tingkat pengguna mengekspresikan semua detail. Anda memiliki pointer, referensi,,
const
operator dereferencing, operator tidak langsung, alamat, dll. Jika Anda memasukkan manajemen memori ke dalam bahasa itu sendiri, sintaksis dapat dirancang sekitar itu. Semua operator itu hilang dan bahasa menjadi lebih bersih dan sederhana.Anda mendapatkan pengembalian investasi yang tinggi. Nilai yang dihasilkan oleh setiap potongan kode dikalikan dengan jumlah orang yang menggunakannya. Ini berarti bahwa semakin banyak pengguna yang Anda miliki, semakin banyak yang mampu Anda keluarkan untuk sebuah perangkat lunak. Saat Anda memindahkan fitur ke dalam bahasa, semua pengguna bahasa akan menggunakannya. Ini berarti Anda dapat mengalokasikan lebih banyak upaya untuk itu daripada yang Anda bisa ke perpustakaan hanya digunakan oleh subset dari pengguna tersebut. Inilah sebabnya mengapa bahasa seperti Java dan C # memiliki VM yang benar-benar kelas satu dan pengumpul sampah berkualitas tinggi yang fantastis: biaya pengembangannya diamortisasi pada jutaan pengguna.
sumber
Dispose
objek yang merangkum bitmap, referensi apa pun ke objek itu akan menjadi referensi ke objek bitmap yang dibuang. Jika objek dihapus sebelum waktunya sementara kode lain masih berharap untuk menggunakannya, kelas bitmap dapat memastikan bahwa kode lain akan gagal dengan cara yang dapat diprediksi . Sebaliknya, menggunakan referensi ke memori yang dibebaskan adalah Perilaku Tidak Terdefinisi.Pengumpulan sampah pada dasarnya hanya berarti bahwa objek yang dialokasikan Anda secara otomatis dirilis pada beberapa titik setelah mereka tidak dapat dijangkau lagi.
Lebih tepatnya, mereka dilepaskan ketika mereka menjadi tidak terjangkau untuk program ini, karena objek yang dirujuk secara melingkar tidak akan pernah dirilis sebaliknya.
Pointer pintar hanya merujuk pada struktur apa pun yang berperilaku seperti pointer biasa tetapi memiliki beberapa fungsi tambahan yang terpasang. Ini termasuk tetapi tidak terbatas pada deallokasi, tetapi juga copy-on-write, cek terikat, ...
Sekarang, seperti yang telah Anda nyatakan, smart pointer dapat digunakan untuk mengimplementasikan bentuk pengumpulan sampah.
Tetapi alur pemikiran berjalan seperti berikut:
Tentu saja, Anda dapat mendesainnya seperti ini dari awal. C # dirancang untuk menjadi sampah yang dikumpulkan, jadi hanya
new
objek Anda dan itu akan dirilis ketika referensi keluar dari ruang lingkup. Cara ini dilakukan terserah kompiler.Namun di C ++, tidak ada pengumpulan sampah yang dimaksudkan. Jika kita mengalokasikan beberapa pointer
int* p = new int;
dan jatuh keluar dari ruang lingkup,p
itu sendiri dihapus dari stack, tetapi tidak ada yang mengurus memori yang dialokasikan.Sekarang satu-satunya yang Anda miliki dari awal adalah destruktor deterministik . Ketika suatu objek meninggalkan ruang lingkup yang telah dibuat, destruktornya disebut. Dalam kombinasi dengan templat dan overloading operator, Anda bisa mendesain objek pembungkus yang berperilaku seperti pointer, tetapi menggunakan fungsionalitas destruktor untuk membersihkan sumber daya yang melekat padanya (RAII). Anda menyebut ini penunjuk cerdas .
Ini semua sangat spesifik C ++: Overloading operator, templat, destruktor, ... Dalam situasi bahasa khusus ini, Anda telah mengembangkan pointer cerdas untuk memberi Anda GC yang Anda inginkan.
Tetapi jika Anda merancang bahasa dengan GC dari awal, ini hanyalah detail implementasi. Anda hanya mengatakan objek akan dibersihkan dan kompiler akan melakukan ini untuk Anda.
Pointer pintar seperti dalam C ++ tidak mungkin bahkan mungkin dalam bahasa seperti C #, yang tidak memiliki kehancuran deterministik sama sekali (C # bekerja di sekitar ini dengan menyediakan gula sintaksis untuk memanggil
.Dispose()
objek tertentu). Sumber daya yang tidak direferensikan akhirnya akan direklamasi oleh GC, tetapi tidak ditentukan kapan tepatnya ini akan terjadi.Dan ini, pada gilirannya, dapat memungkinkan GC melakukan tugasnya dengan lebih efisien. Dibangun lebih dalam ke dalam bahasa daripada smart pointer, yang ditetapkan di atasnya, .NET GC misalnya dapat menunda operasi memori dan melakukan mereka dalam blok untuk membuatnya lebih murah atau bahkan memindahkan memori untuk meningkatkan efisiensi berdasarkan seberapa sering objek diakses.
sumber
IDisposable
danusing
. Tetapi membutuhkan sedikit upaya programmer, itulah sebabnya biasanya hanya digunakan untuk sumber daya yang sangat langka seperti pegangan koneksi database.IDisposable
sintaksis yang lebih sederhana dengan hanya mengganti konvensionallet ident = value
denganuse ident = value
...using
tidak ada hubungannya dengan pengumpulan sampah sama sekali, itu hanya memanggil fungsi ketika variabel jatuh keluar dari ruang lingkup seperti destruktor di C ++.Menurut saya, ada dua perbedaan besar antara pengumpulan sampah dan petunjuk pintar yang digunakan untuk manajemen memori:
Yang pertama berarti bahwa GC akan mengumpulkan sampah yang pointer pintar tidak akan; jika Anda menggunakan smart pointer, Anda harus menghindari membuat sampah semacam ini, atau bersiap untuk menanganinya secara manual.
Yang terakhir berarti bahwa tidak peduli seberapa pintar pointer cerdas, operasi mereka akan memperlambat utas dalam program Anda. Pengumpulan sampah dapat menunda pekerjaan, dan memindahkannya ke utas lainnya; yang membuatnya menjadi lebih efisien secara keseluruhan (memang, biaya runtime dari GC modern kurang dari sistem malloc / bebas normal, bahkan tanpa overhead tambahan dari smart pointer), dan melakukan pekerjaan apa yang masih perlu dilakukan tanpa masuk ke dalam cara utas aplikasi.
Sekarang, perhatikan bahwa smart pointer, yang merupakan konstruksi terprogram, dapat digunakan untuk melakukan segala macam hal menarik lainnya - lihat jawaban Dario - yang sepenuhnya berada di luar ruang lingkup pengumpulan sampah. Jika Anda ingin melakukan itu, Anda akan memerlukan smart pointer.
Namun, untuk keperluan manajemen memori, saya tidak melihat adanya prospek smart pointer menggantikan pengumpulan sampah. Mereka sama sekali tidak ahli dalam hal itu.
sumber
using
blok dalam versi C # berikutnya. Selain itu, perilaku nondeterministik dari GC dapat dilarang dalam sistem waktu nyata (itulah sebabnya mengapa GC tidak digunakan di sana). Juga, jangan lupa bahwa GC sangat rumit untuk diperbaiki sehingga sebagian besar sebenarnya bocor memori dan sangat tidak efisien (misalnya Boehm ...).Istilah pengumpulan sampah berarti ada sampah yang harus dikumpulkan. Dalam C ++ pointer cerdas datang dalam berbagai rasa, yang terpenting adalah unique_ptr. Unique_ptr pada dasarnya adalah kepemilikan tunggal dan konstruksi pelingkupan. Dalam bagian kode yang dirancang dengan baik, sebagian besar tumpukan item yang dialokasikan biasanya berada di belakang pointer smart unique_ptr dan kepemilikan sumber daya tersebut akan didefinisikan dengan baik setiap saat. Nyaris tidak ada overhead dalam unique_ptr dan unique_ptr menghilangkan sebagian besar masalah manajemen memori manual yang secara tradisional mengarahkan orang ke bahasa yang dikelola. Sekarang karena lebih banyak core yang berjalan bersamaan menjadi barang yang lebih umum, prinsip-prinsip desain yang mendorong kode untuk menggunakan kepemilikan yang unik dan terdefinisi dengan baik pada setiap saat menjadi lebih penting untuk kinerja.
Bahkan dalam program yang dirancang dengan baik, terutama di lingkungan multi-threaded, tidak semuanya dapat diekspresikan tanpa struktur data bersama, dan untuk struktur data yang benar-benar membutuhkan, thread perlu berkomunikasi. RAII di c ++ berfungsi cukup baik untuk masalah seumur hidup dalam satu pengaturan ulir, dalam pengaturan multi-ulir, masa pakai objek mungkin tidak sepenuhnya ditumpuk secara hierarki. Untuk situasi ini, penggunaan shared_ptr menawarkan sebagian besar solusi. Anda membuat kepemilikan bersama atas sumber daya dan ini di C ++ adalah satu-satunya tempat kita melihat sampah, tetapi dalam jumlah kecil sehingga program c ++ yang dirancang dengan baik harus dianggap lebih untuk menerapkan pengumpulan 'sampah' dengan berbagi-ptr daripada pengumpulan sampah lengkap sebagai diimplementasikan dalam bahasa lain. C ++ tidak memiliki 'sampah' sebanyak itu
Seperti yang dinyatakan oleh orang lain, referensi yang dihitung pointer pintar adalah salah satu bentuk pengumpulan sampah, dan untuk itu memiliki satu masalah besar. Contoh yang digunakan sebagian besar sebagai kelemahan referensi bentuk dihitung dari pengumpulan sampah adalah masalah dengan pembuatan struktur data yatim yang terhubung dengan smart pointer satu sama lain yang membuat cluster objek yang menjaga satu sama lain dari yang dikumpulkan. Sementara dalam program yang dirancang sesuai dengan aktor-model perhitungan, struktur data biasanya tidak memungkinkan untuk cluster yang tidak dapat dikumpulkan tersebut muncul di C ++, ketika Anda menggunakan pendekatan data bersama yang luas untuk pemrograman multi-threaded, seperti yang digunakan sebagian besar dari industri, cluster yatim ini dapat dengan cepat menjadi kenyataan.
Jadi untuk meringkas semuanya, jika dengan penggunaan pointer bersama Anda berarti penggunaan unique_ptr yang luas dikombinasikan dengan model aktor dari pendekatan komputasi untuk pemrograman multi-threaded dan penggunaan shared_ptr yang terbatas, daripada bentuk pengumpulan sampah lainnya tidak membelikan Anda apa pun manfaat tambahan. Namun, jika pendekatan segalanya yang dibagikan akan membuat Anda berakhir dengan shared_ptr di semua tempat, daripada Anda harus mempertimbangkan beralih model mata uang atau beralih ke bahasa yang dikelola yang lebih diarahkan pada pembagian kepemilikan yang lebih luas dan akses bersamaan ke struktur data.
sumber
Rust
tidak perlu pengumpulan sampah?Kebanyakan pointer pintar diimplementasikan menggunakan penghitungan referensi. Artinya, setiap penunjuk pintar yang merujuk ke objek menambah jumlah referensi objek. Ketika hitungan itu menjadi nol, objek dilepaskan.
Masalahnya ada jika Anda memiliki referensi melingkar. Artinya, A memiliki referensi ke B, B memiliki referensi ke C dan C memiliki referensi ke A. Jika Anda menggunakan pointer pintar, maka untuk membebaskan memori yang terkait dengan A, B & C Anda harus secara manual dapatkan di sana "istirahat" referensi melingkar (misalnya menggunakan
weak_ptr
dalam C ++).Pengumpulan sampah (biasanya) bekerja sangat berbeda. Sebagian besar pengumpul sampah akhir-akhir ini menggunakan uji jangkauan . Yaitu, ia melihat semua referensi pada stack dan yang dapat diakses secara global dan kemudian melacak setiap objek yang dirujuk oleh referensi tersebut, dan objek yang mereka rujuk, dll. Semua yang lain adalah sampah.
Dengan cara itu, referensi melingkar tidak penting lagi - selama A, B dan C tidak dapat dijangkau , memori dapat direklamasi.
Ada keuntungan lain dari pengumpulan sampah "nyata". Sebagai contoh, alokasi memori sangat murah: hanya menambah pointer ke "ujung" blok memori. Deallokasi juga memiliki biaya diamortisasi yang konstan. Tapi tentu saja bahasa seperti C ++ memungkinkan Anda untuk mengimplementasikan manajemen memori dengan cara apa pun yang Anda suka, sehingga Anda bisa membuat strategi alokasi yang lebih cepat.
Tentu saja, di C ++ jumlah memori yang dialokasikan heap biasanya kurang dari bahasa referensi-berat seperti C # /. NET. Tapi itu bukan masalah pengumpulan sampah vs smart pointer.
Dalam kasus apa pun, masalahnya bukan salah satu yang lebih baik daripada yang lain. Mereka masing-masing memiliki kelebihan dan kekurangan.
sumber
Ini tentang kinerja . Mengosongkan memori membutuhkan banyak administrasi. Jika unallocation berjalan di latar belakang, kinerja proses foreground meningkat. Sayangnya, alokasi memori tidak bisa malas (objek yang dialokasikan akan digunakan pada momen suci berikutnya), tetapi melepaskan objek bisa.
Coba di C ++ (tanpa GC) untuk mengalokasikan banyak objek, cetak "halo", lalu hapus. Anda akan terkejut berapa lama waktu yang dibutuhkan untuk benda bebas.
Juga, GNU libc menyediakan alat yang lebih efektif untuk tidak mengalokasikan memori, lihat rintangan . Harus memperhatikan, saya tidak punya pengalaman dengan rintangan, saya tidak pernah menggunakannya.
sumber
Pengumpulan sampah bisa lebih efisien - ini pada dasarnya 'menambah' overhead manajemen memori dan melakukan semuanya sekaligus. Secara umum ini akan menghasilkan CPU kurang keseluruhan yang dikeluarkan pada alokasi memori, tetapi itu berarti bahwa Anda akan memiliki ledakan besar aktivitas de-alokasi di beberapa titik. Jika GC tidak dirancang dengan benar, ini dapat menjadi terlihat oleh pengguna sebagai 'jeda' sementara GC mencoba untuk mengalokasikan memori. Sebagian besar GC modern sangat pandai menjaga hal ini tidak terlihat oleh pengguna kecuali dalam kondisi yang paling buruk.
Pointer pintar (atau skema penghitungan referensi) memiliki keuntungan bahwa hal itu terjadi tepat ketika Anda harapkan dari melihat kode (pointer pintar keluar dari ruang lingkup, sesuatu akan dihapus). Anda mendapatkan sedikit de-alokasi di sana-sini. Anda secara keseluruhan dapat menggunakan lebih banyak waktu CPU untuk de-alokasi, tetapi karena itu tersebar di semua hal yang terjadi dalam program Anda, itu lebih kecil kemungkinannya (kecuali de-alokasi beberapa struktur data monster) agar terlihat oleh pengguna Anda.
Jika Anda melakukan sesuatu di mana masalah responsif, saya akan menyarankan bahwa smart pointer / penghitungan memberi tahu Anda secara tepat kapan sesuatu terjadi, sehingga Anda bisa tahu sambil mengkode apa yang mungkin terlihat oleh pengguna Anda. Dalam pengaturan GC Anda hanya memiliki kontrol sesaat atas pengumpul sampah dan hanya perlu mencoba untuk menyelesaikannya.
Di sisi lain, jika throughput keseluruhan adalah tujuan Anda, sistem berbasis GC mungkin merupakan pilihan yang jauh lebih baik, karena meminimalkan sumber daya yang diperlukan untuk melakukan manajemen memori.
Siklus: Saya tidak menganggap masalah siklus sebagai masalah yang signifikan. Dalam sistem di mana Anda memiliki pointer cerdas, Anda cenderung ke arah struktur data yang tidak memiliki siklus, atau Anda hanya berhati-hati tentang bagaimana Anda melepaskan hal-hal seperti itu. Jika perlu, benda penjaga yang tahu cara memutus siklus di benda yang dimiliki dapat digunakan untuk memastikan kerusakan yang tepat secara otomatis. Dalam beberapa bidang pemrograman ini mungkin penting, tetapi untuk sebagian besar pekerjaan sehari-hari, ini tidak relevan.
sumber
Keterbatasan dari smart pointer adalah mereka tidak selalu membantu terhadap referensi melingkar. Misalnya Anda memiliki objek A yang menyimpan penunjuk pintar ke objek B dan objek B menyimpan penunjuk pintar ke objek A. Jika dibiarkan bersama tanpa menyetel ulang salah satu pointer, mereka tidak akan pernah dibatalkan alokasi.
Ini terjadi karena penunjuk pintar harus melakukan tindakan tertentu yang tidak akan di-trivia dalam skenario di atas karena kedua objek tidak dapat dijangkau oleh program. Pengumpulan sampah akan mengatasinya - itu akan mengidentifikasi dengan benar bahwa objek tidak dapat dijangkau oleh program dan mereka akan dikumpulkan.
sumber
Ini spektrum .
Jika Anda tidak terikat ketat pada kinerja dan siap untuk mengerjakan sesuatu, Anda akan berakhir pada pertemuan atau c, dengan semua tanggung jawab pada Anda untuk membuat keputusan yang tepat dan semua kebebasan untuk melakukan itu, tetapi dengan itu , semua kebebasan untuk mengacaukannya:
"Aku akan memberitahumu apa yang harus dilakukan, lakukan saja. Percayalah padaku".
Pengumpulan sampah adalah ujung lain dari spektrum. Anda memiliki kontrol yang sangat kecil, tetapi diurus untuk Anda:
"Aku akan memberitahumu apa yang aku inginkan, kamu mewujudkannya".
Ini memiliki banyak keuntungan, sebagian besar Anda tidak perlu dapat dipercaya ketika datang untuk mengetahui kapan sumber daya tidak lagi dibutuhkan, tetapi (meskipun beberapa jawaban mengambang di sini) tidak baik untuk kinerja, dan prediktabilitas kinerja. (Seperti semua hal, jika Anda diberi kendali, dan melakukan sesuatu yang bodoh Anda dapat memiliki hasil yang lebih buruk. Namun untuk menyarankan bahwa mengetahui pada waktu kompilasi apa kondisi untuk dapat membebaskan memori, tidak dapat digunakan sebagai kemenangan kinerja adalah melampaui naif).
RAII, pelingkupan, penghitungan ref, dll semuanya adalah pembantu untuk membiarkan Anda bergerak lebih jauh di sepanjang spektrum itu tetapi tidak semua jalan di sana. Semua hal ini masih membutuhkan penggunaan aktif. Mereka masih membiarkan dan mengharuskan Anda untuk berinteraksi dengan manajemen memori dengan cara pengumpulan sampah tidak.
sumber
Harap diingat bahwa pada akhirnya, semuanya bermuara pada instruksi pelaksanaan CPU. Sepengetahuan saya, semua CPU tingkat konsumen memiliki set instruksi yang mengharuskan Anda memiliki data yang disimpan di tempat tertentu dalam memori dan Anda memiliki petunjuk untuk data tersebut. Itu semua yang Anda miliki di tingkat dasar.
Semua yang ada di atas itu dengan pengumpulan sampah, referensi ke data yang mungkin telah dipindahkan, tumpukan pemadatan, dll. Adalah melakukan pekerjaan dalam batasan yang diberikan oleh paradigma "memori sepotong dengan penunjuk alamat" di atas. Hal yang sama dengan pointer pintar - Anda MASIH harus membuat kode berjalan pada perangkat keras yang sebenarnya.
sumber