Bagaimana seorang pengumpul sampah mencegah seluruh memori dipindai pada setiap pengumpulan?

16

Beberapa (paling tidak Mono dan .NET) pemulung memiliki area memori jangka pendek yang sering mereka pindai, dan area memori sekunder yang jarang mereka pindai. Mono menyebut ini kamar anak-anak.

Untuk mengetahui objek yang dapat dibuang, mereka memindai semua objek mulai dari root, tumpukan dan register dan membuang semua objek yang tidak dirujuk lagi.

Pertanyaan saya adalah bagaimana mereka mencegah semua memori terpakai dipindai pada setiap pengambilan? Pada prinsipnya, satu-satunya cara untuk mengetahui objek apa yang tidak digunakan lagi adalah memindai semua objek dan semua referensi mereka. Namun, ini akan mencegah OS dari menukar memori meskipun itu tidak digunakan oleh aplikasi dan terasa seperti sejumlah besar pekerjaan yang perlu dilakukan, juga untuk "Koleksi Pembibitan". Tidak terasa mereka menang banyak dengan menggunakan kamar bayi.

Apakah saya kehilangan sesuatu atau apakah pemulung benar-benar memindai setiap objek dan setiap referensi setiap kali melakukan pengumpulan?

Pieter van Ginkel
sumber
1
tinjauan yang bagus ada dalam artikel Tuning Pengumpulan Sampah Seni yang ditulis oleh Angelika Langer. Secara formal, ini adalah tentang bagaimana hal itu dilakukan di Jawa, tetapi konsep yang disajikan cukup banyak bahasa agnostik
nyamuk

Jawaban:

14

Pengamatan mendasar yang memungkinkan pengumpulan sampah generasi untuk menghindari keharusan memindai semua objek generasi yang lebih tua adalah:

  1. Setelah koleksi, semua objek yang masih ada akan memiliki beberapa generasi minimum (mis. Dalam .net, setelah koleksi Gen0, semua objek adalah Gen1 atau Gen2; setelah koleksi Gen1 atau Gen2, semua objek adalah Gen2).
  2. Sebuah objek, atau bagian daripadanya, yang belum ditulis sejak koleksi yang mempromosikan semuanya ke generasi N atau lebih tinggi tidak dapat berisi referensi ke objek dari generasi yang lebih rendah.
  3. Jika suatu objek telah mencapai generasi tertentu, objek tersebut tidak perlu diidentifikasi dapat dijangkau untuk memastikan retensi ketika mengumpulkan generasi yang lebih rendah.

Dalam banyak kerangka kerja GC, mungkin bagi pemulung untuk menandai objek atau bagiannya sedemikian rupa sehingga upaya pertama untuk menulis kepada mereka akan memicu kode khusus untuk mencatat fakta bahwa mereka telah dimodifikasi. Objek atau bagiannya yang telah dimodifikasi, terlepas dari generasinya, harus dipindai dalam koleksi berikutnya, karena dapat berisi referensi ke objek yang lebih baru. Di sisi lain, sangat umum untuk ada banyak objek yang lebih tua yang tidak bisa dimodifikasi di antara koleksi. Fakta bahwa pemindaian generasi yang lebih rendah dapat mengabaikan objek tersebut dapat memungkinkan pemindaian tersebut untuk menyelesaikan lebih cepat daripada yang seharusnya.

Perhatikan, btw, bahwa bahkan jika seseorang tidak dapat mendeteksi kapan objek dimodifikasi dan harus memindai semuanya pada setiap pass GC, pengumpulan sampah generasi masih dapat meningkatkan kinerja tahap "sweep" dari kolektor pemadatan. Dalam beberapa lingkungan tertanam (terutama di mana ada sedikit atau tidak ada perbedaan dalam kecepatan antara akses memori sekuensial dan acak), memindahkan blok memori sekitar relatif mahal dibandingkan dengan menandai tag. Akibatnya, bahkan jika fase "tanda" tidak dapat dipercepat menggunakan kolektor generasi, mempercepat fase "sapuan" mungkin bermanfaat.

supercat
sumber
memindahkan blok memori mahal di sistem apa pun, jadi meningkatkan penyapuan adalah keuntungan bahkan pada sistem CPU quad Ghz Anda.
gbjbaanb
@ gbjbaanb: Dalam banyak kasus, biaya pemindaian segala sesuatu untuk menemukan objek hidup akan signifikan dan tidak dapat diterima bahkan jika memindahkan objek benar-benar gratis. Akibatnya, orang harus ketika praktis menghindari pemindaian benda-benda tua. Di sisi lain, menahan diri dari memadatkan objek yang lebih tua adalah optimasi sederhana yang dapat dilakukan bahkan pada kerangka kerja sederhana. BTW, jika seseorang merancang kerangka kerja GC untuk sistem tertanam kecil, dukungan deklaratif untuk objek yang tidak dapat diubah bisa membantu. Melacak apakah objek yang dapat berubah telah berubah itu sulit, tetapi orang mungkin bisa melakukannya dengan baik ...
supercat
... anggap saja objek yang bisa berubah perlu dipindai setiap pass GC tetapi objek yang tidak dapat diubah tidak. Bahkan jika satu-satunya cara untuk membangun objek yang tidak dapat diubah adalah dengan membangun "prototipe" dalam ruang yang bisa berubah dan kemudian menyalinnya, operasi salinan tambahan dapat menghindari kebutuhan untuk memindai objek dalam operasi GC di masa depan.
supercat
Kebetulan, kinerja pengumpulan sampah pada implementasi BASIC yang diperoleh Microsoft tahun 1980 untuk 6502 mikroprosesor (dan mungkin yang lain juga) dapat sangat ditingkatkan dalam beberapa kasus, jika sebuah program yang menghasilkan banyak string yang tidak akan pernah berubah, disalin alokasi string "pointer ke pointer" atas ruang string ". Perubahan seperti itu akan mencegah pengumpul sampah memeriksa string lama untuk melihat apakah mereka masih diperlukan. Commodore 64 hampir tidak berteknologi tinggi, tetapi GC "generasional" semacam itu akan membantu bahkan di sana.
supercat
7

GC yang Anda maksud adalah pengumpul sampah generasi . Mereka direkayasa untuk mendapatkan hasil maksimal dari pengamatan yang dikenal sebagai "kematian bayi" atau "hipotesis generasi", yang berarti bahwa sebagian besar objek menjadi tidak terjangkau dengan sangat cepat. Mereka memang memindai mulai dari akar, tetapi mengabaikan semua benda lama . Oleh karena itu, mereka tidak perlu memindai sebagian besar objek dalam memori, mereka hanya memindai objek muda (dengan mengorbankan tidak mendeteksi objek lama yang tidak dapat dijangkau, setidaknya tidak pada saat itu).

"Tapi itu salah", saya mendengar Anda berteriak, "benda-benda tua dapat dan memang merujuk pada benda-benda muda". Anda benar, dan ada beberapa solusi untuk itu, yang semuanya berputar di sekitar mendapatkan pengetahuan, dengan cepat dan efisien, benda-benda tua mana yang harus diperiksa dan mana yang aman untuk diabaikan. Mereka cukup banyak mendidih ke objek rekaman, atau rentang memori kecil (lebih besar dari objek, tetapi jauh lebih kecil dari seluruh tumpukan) yang berisi pointer ke generasi yang lebih muda. Orang lain telah menggambarkan hal-hal yang jauh lebih baik daripada saya, jadi saya hanya akan memberi Anda beberapa kata kunci: Penandaan kartu, set ingat, menulis hambatan. Ada teknik lain juga (termasuk hibrida), tetapi ini mencakup pendekatan umum yang saya ketahui.


sumber
3

Untuk mengetahui objek pembibitan apa yang masih hidup, kolektor hanya perlu memindai kumpulan root dan benda lama yang telah dimutasi sejak koleksi terakhir , karena objek lama yang belum mutasi baru-baru ini tidak dapat mengarah ke objek muda . Ada berbagai algoritme untuk mempertahankan informasi ini pada berbagai tingkat presisi (dari set bidang mutasi yang tepat hingga set halaman di mana mutasi mungkin terjadi), tetapi semuanya umumnya melibatkan semacam semacam penghalang tulis : kode yang berjalan pada setiap referensi mutasi bidang -typed yang memperbarui pembukuan GC.

Ryan Culpepper
sumber
1

Generasi pengumpul sampah tertua dan paling sederhana benar-benar memindai semua memori, dan harus menghentikan semua pemrosesan lainnya saat mereka melakukannya. Algoritme kemudian ditingkatkan dalam hal ini dengan berbagai cara - membuat penambahan / pemindaian, atau berjalan secara paralel. Sebagian besar pengumpul sampah modern memisahkan objek menjadi beberapa generasi, dan mengelola dengan hati-hati petunjuk lintas generasi sehingga generasi yang lebih baru dapat dikumpulkan tanpa mengganggu yang lebih tua.

Poin kuncinya adalah bahwa pengumpul sampah bekerja dalam kolaborasi erat dengan kompiler dan dengan sisa runtime untuk mempertahankan ilusi bahwa ia mengawasi semua memori.

ddyer
sumber
Saya tidak yakin pendekatan pengumpulan sampah apa yang digunakan dalam minicomputer dan mainframe sebelum akhir 1970-an, tetapi pengumpul sampah Microsoft BASIC, setidaknya pada 6502 mesin, akan mengatur penunjuk "string berikutnya" ke atas memori, dan kemudian mencari semua referensi string untuk menemukan alamat tertinggi yang berada di bawah "penunjuk string berikutnya". String itu akan disalin tepat di bawah "pointer string berikutnya", dan pointer itu akan diparkir tepat di bawahnya. Algoritma kemudian akan diulang. Itu mungkin bagi kode untuk membawa sial pointer yang memberikan ...
supercat
... Sesuatu seperti koleksi generasi. Saya kadang-kadang bertanya-tanya betapa sulitnya untuk menambal BASIC untuk mengimplementasikan koleksi "generasional" dengan hanya menyimpan alamat bagian atas setiap generasi, dan menambahkan beberapa operasi penunjuk-swap sebelum dan sesudah setiap siklus GC. Kinerja GC masih akan sangat buruk, tetapi mungkin dalam banyak kasus dicukur dari puluhan detik menjadi sepersepuluh detik.
supercat
-2

Pada dasarnya ... GC menggunakan "ember" untuk memisahkan apa yang sedang digunakan dan apa yang tidak. Setelah membuatnya memeriksa, menghapus hal-hal yang tidak digunakan dan memindahkan semuanya ke generasi ke-2 (yang jarang diperiksa dari generasi ke-1) dan kemudian memindahkan hal-hal yang masih digunakan di ruang ke-2 ke gen ke-3.

Jadi, hal-hal dalam generasi ke-3 biasanya benda yang macet karena alasan tertentu, dan GC tidak sering memeriksa di sana.

aserwin
sumber
1
Tapi bagaimana ia tahu benda apa yang digunakan?
Pieter van Ginkel
Ini melacak objek mana yang dapat dijangkau dari kode yang dapat dijangkau. Setelah suatu objek tidak lagi dapat dijangkau dari kode apa pun yang dapat mengeksekusi (katakanlah, kode untuk metode yang telah kembali) maka GC tahu itu aman untuk dikumpulkan
JohnL
Kalian berdua menggambarkan bagaimana GC benar, bukan bagaimana mereka efisien. Dilihat dari pertanyaan, OP tahu itu sepenuhnya.
@delnan ya saya menjawab pertanyaan tentang bagaimana ia tahu objek mana yang digunakan, yang adalah apa yang ada di komentar Pieter.
JohnL
-5

Algoritma yang biasanya digunakan oleh GC ini adalah Naïve mark-and-sweep

Anda juga harus menyadari fakta bahwa ini bukan dikelola oleh C # itu sendiri, tetapi oleh yang disebut CLR .

pengguna827992
sumber
Itulah perasaan yang saya dapatkan dari membaca tentang pengumpul sampah Mono. Namun, apa yang saya tidak mengerti adalah mengapa jika mereka memindai set kerja lengkap yang pernah dikumpulkan, mereka memiliki kolektor generasi dengan mana koleksi GEN-0 sangat cepat. Bagaimana ini bisa cepat dengan set kerja katakanlah 2GB?
Pieter van Ginkel
baik, GC sebenarnya untuk mono adalah Sgen, Anda harus membaca mono-project.com/Generational_GC ini atau beberapa artikel online schani.wordpress.com/tag/mono infoq.com/news/2011/01/SGen , intinya adalah bahwa ini teknologi baru seperti CLR dan CLI memiliki desain yang benar-benar modular, bahasa menjadi hanya cara untuk mengekspresikan sesuatu untuk CLR dan bukan cara untuk menghasilkan kode biner. Pertanyaan Anda adalah tentang detail implementasi dan bukan tentang algoritma, karena suatu algoritma masih belum memiliki implementasi, Anda harus membaca makalah teknis dan artikel dari Mono, tidak ada orang lain.
user827992
Saya bingung. Strategi yang digunakan seorang pemulung bukanlah sebuah algoritma?
Pieter van Ginkel
2
-1 Hentikan OP yang membingungkan. Bahwa GC adalah bagian dari CLR dan tidak spesifik bahasa tidak relevan sama sekali. GC sebagian besar ditandai oleh cara memaparkan heap dan menentukan jangkauan, dan yang terakhir adalah semua tentang algoritma yang digunakan untuk itu. Meskipun ada banyak implementasi dari suatu algoritma, dan Anda tidak boleh terjebak dalam detail implementasi, algoritma itu sendiri menentukan berapa banyak objek yang dipindai. GC generasi hanyalah sebuah algoritma + heap layout yang mencoba memanfaatkan "hipotesis generasi" (bahwa sebagian besar objek mati muda). Ini bukan naif.
4
Algoritma! = Implementasi memang, tetapi implementasi hanya dapat menyimpang sejauh ini sebelum menjadi implementasi dari algoritma yang berbeda. Deskripsi algoritma, di dunia GC, sangat spesifik dan mencakup hal-hal seperti tidak memindai seluruh tumpukan pada koleksi pembibitan dan bagaimana pointer antar generasi ditemukan dan disimpan. Memang benar bahwa suatu algoritma tidak memberi tahu Anda berapa lama suatu langkah spesifik dari algoritma akan mengambil, tetapi itu sama sekali tidak relevan dengan pertanyaan ini.