it-swarm-id.com

Mengapa Pengumpulan Sampah jika ada pointer pintar

Saat ini, banyak sekali bahasa yang dikumpulkan dari sampah. Itu 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, lalu mengapa tidak demikian?

69
Gulshan

Jadi, apa gunanya menggunakan pengumpulan sampah?

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 lain pengumpulan sampah 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 GC 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 lebih banyak dan bahkan bisa waktu nyata, mis. Treadmill milik Baker.

71
Jon Harrop

Karena tidak ada yang melihatnya dari sudut ini, saya akan ulangi pertanyaan Anda: mengapa memasukkan sesuatu ke dalam bahasa jika Anda dapat melakukannya di perpustakaan? Mengabaikan implementasi spesifik dan detail sintaksis, GC/pintar pointer 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:

  1. Anda memastikan bahwa semua kode dapat menggunakannya untuk beroperasi. Ini, saya pikir, alasan utama mengapa kode digunakan kembali dan kode berbagi tidak benar-benar lepas landas hingga 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++, Anda mungkin mengalami rasa sakit yang 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 menjadikannya sebagai non-isu yang lengkap.

  2. 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.

  3. 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 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 sebagian 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.

66
munificent

Pengumpulan sampah pada dasarnya hanya berarti bahwa objek Anda yang dialokasikan secara otomatis dirilis pada beberapa titik setelah mereka tidak dapat dijangkau lagi.

Lebih akurat, mereka dilepaskan ketika mereka menjadi tidak dapat dijangkau untuk program, karena objek yang dirujuk secara melingkar tidak akan pernah dirilis sebaliknya.

Smart pointer cukup merujuk ke struktur apa pun yang berperilaku seperti pointer biasa tetapi memiliki beberapa fungsi tambahan yang terpasang. Ini termasuk tetapi tidak terbatas pada deallokasi, tetapi juga salin-tulis, cek terikat, ...

Sekarang, seperti yang telah Anda nyatakan, smart pointer dapat digunakan untuk mengimplementasikan bentuk pengumpulan sampah.

Tetapi alur pemikiran berjalan seperti berikut:

  1. Pengumpulan sampah adalah hal yang keren untuk dimiliki, karena nyaman dan saya harus mengurus lebih sedikit barang
  2. Karena itu: Saya ingin pengumpulan sampah dalam bahasa saya
  3. Sekarang, bagaimana cara memasukkan GC ke dalam bahasa saya?

Tentu saja, Anda dapat mendesainnya seperti ini dari awal. C # adalah dirancang sebagai sampah yang dikumpulkan, jadi hanya new objek Anda dan itu akan dirilis ketika referensi berada di luar 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 di luar cakupan, 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 template dan kelebihan operator, Anda dapat merancang 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 pintar 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++ bahkan mungkin tidak akan mungkin dalam bahasa seperti C # 's, yang tidak memiliki kehancuran deterministik sama sekali (C # bekerja di sekitar ini dengan menyediakan gula sintaksis untuk memanggil .Dispose() pada objek tertentu). Sumber daya yang tidak direferensikan akan akhirnya 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 dapat mis. menunda operasi memori dan melakukannya dalam blok untuk membuatnya lebih murah atau bahkan memindahkan memori sekitar untuk meningkatkan efisiensi berdasarkan seberapa sering objek diakses.

36
Dario

Menurut saya, ada dua perbedaan besar antara pengumpulan sampah dan petunjuk pintar yang digunakan untuk manajemen memori:

  1. Pointer pintar tidak dapat mengumpulkan sampah siklik; pengumpulan sampah bisa
  2. Pointer pintar melakukan semua pekerjaan pada saat referensi, dereferencing, dan deallokasi, pada utas aplikasi; pengumpulan sampah tidak perlu

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 tidak begitu mahir dalam hal itu.

4
Tom Anderson

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 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, semakin 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 titik waktu menjadi lebih penting untuk kinerja. Penggunaan model aktor perhitungan memungkinkan untuk pembangunan program dengan jumlah minimum berbagi negara di antara utas, dan kepemilikan unik memainkan peran utama dalam membuat sistem kinerja tinggi membuat penggunaan efisien banyak core tanpa biaya overhead dibagi-antara- utas data dan persyaratan mutex yang tersirat.

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, utas 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 hierarkis. 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 pengumpulan bersama-ptr daripada pengumpulan sampah lengkap sebagai diimplementasikan dalam bahasa lain. C++ tidak memiliki banyak 'sampah' untuk dikumpulkan.

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 pendekatan komputasi untuk pemrograman multi-threaded dan penggunaan shared_ptr yang terbatas, daripada bentuk pengumpulan sampah lainnya, tidak ada gunanya bagi Anda. manfaat tambahan. Namun, jika pendekatan berbagi segalanya akan membuat Anda berakhir dengan shared_ptr di semua tempat, daripada Anda harus mempertimbangkan untuk beralih model concurrency atau beralih ke bahasa yang dikelola yang lebih diarahkan pada pembagian kepemilikan yang lebih luas dan akses bersamaan ke struktur data.

4
user1703394

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 ji 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 mereka merujuk, dll. Semua yang lain adalah sampah .

Dengan cara itu, referensi melingkar tidak penting lagi - asalkan A, B dan C tidak dapat dijangka, memori dapat direklamasi.

Ada keuntungan lain dari pengumpulan sampah "nyata". Sebagai contoh, alokasi memori sangat murah: hanya menambah pointer ke "akhir" dari blok memori. Deallokasi memiliki biaya diamortisasi yang konstan juga. Tapi tentu saja bahasa seperti C++ memungkinkan Anda untuk menerapkan 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.

2
Dean Harding

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 saat 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 unallocating memory, lihat rintangan . Harus diperhatikan, saya tidak punya pengalaman dengan rintangan, saya tidak pernah menggunakannya.

2
ern0

Pengumpulan sampah bisa lebih efisien - pada dasarnya 'menambah' overhead manajemen memori dan melakukan semuanya sekaligus. Secara umum ini akan menghasilkan CPU yang kurang keseluruhan 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 baik dalam menjaga 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, kemungkinannya kecil (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.

2
Michael Kohne

Keterbatasan dari smart pointer adalah mereka tidak selalu membantu melawan referensi melingkar. Misalnya Anda memiliki objek A yang menyimpan penunjuk cerdas ke objek B dan objek B menyimpan penunjuk cerdas ke objek A. Jika dibiarkan bersama tanpa mengatur 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.

1
sharptooth

Ini spektrum.

Jika Anda tidak terikat ketat pada kinerja dan siap untuk memasukkan Grind, Anda akan berakhir di Assembly 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 bahwa Anda tidak perlu dapat dipercaya ketika datang untuk mengetahui dengan tepat 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 diberikan kontrol, 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 adalah semua pembantu untuk membiarkan Anda bergerak lebih jauh di sepanjang spektrum itu tetapi tidak semua jalan ke sana. Semua hal ini masih membutuhkan penggunaan aktif. Mereka masih membiarkan dan mengharuskan Anda untuk berinteraksi dengan manajemen memori dengan cara yang pengumpulan sampah tidak.

1
drjpizzle

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.

Segala sesuatu di atas itu dengan pengumpulan sampah, referensi ke data yang mungkin telah dipindahkan, tumpukan pemadatan, dll. Sedang melakukan pekerjaan dalam batasan yang diberikan oleh paradigma "memori chunk dengan penunjuk alamat" di atas. Hal yang sama dengan pointer pintar - Anda MASIH harus membuat kode berjalan pada perangkat keras yang sebenarnya.

0
user1249