Bir çöp toplayıcı, her toplamada belleğin taranmasını nasıl önler?


16

Bazı (en azından Mono ve .NET'ler) çöp toplayıcıların, sık sık taradıkları kısa süreli bir bellek alanı ve daha az sıklıkta taradıkları ikincil bir hafıza alanı vardır. Mono buna kreş diyor.

Hangi nesnelerin atılabileceğini bulmak için, köklerden, yığından ve kayıtlardan başlayarak tüm nesneleri tarar ve artık referans alınmayan tüm nesneleri atarlar.

Benim sorum, kullanılan tüm belleğin her koleksiyonda taranmasını nasıl önledikleri? Prensip olarak, artık hangi nesnelerin kullanılmadığını öğrenmenin tek yolu tüm nesneleri ve tüm referanslarını taramaktır. Ancak bu, uygulama tarafından kullanılmasa da işletim sisteminin belleği değiştirmesini önler ve "Nursery Collection" için de yapılması gereken çok fazla iş gibi hisseder. Bir kreş kullanarak çok fazla kazanıyor gibi hissetmiyorlar.

Bir şey eksik mi veya çöp toplayıcı aslında her koleksiyonda her nesneyi ve her referansı tarar mı?


1
güzel bir genel bakış Angelika Langer tarafından yazılmış Çöp Toplama Tuning Sanatı makalesinde yer almaktadır . Resmi olarak, Java'da nasıl yapıldığı hakkında, ancak sunulan kavramlar hemen hemen dil agnostik
gnat

Yanıtlar:


14

Tüm eski nesil nesneleri taramak zorunda kalmamak için kuşak çöplerinin toplanmasına izin veren temel gözlemler şunlardır:

  1. Bir koleksiyondan sonra, hala var olan tüm nesneler minimum nesil olacaktır (örneğin .net'te, bir Gen0 koleksiyonundan sonra tüm nesneler Gen1 veya Gen2'dir; Gen1 veya Gen2 koleksiyonundan sonra tüm nesneler Gen2'dir).
  2. N neslini veya daha yüksek nesli teşvik eden bir koleksiyondan bu yana yazılmamış olan bir nesne veya bunun bir kısmı, daha düşük nesil nesnelere herhangi bir referans içeremez.
  3. Bir nesne belirli bir nesile ulaştıysa, daha düşük nesiller toplarken elde tutulmasını sağlamak için ulaşılabilir olarak tanımlanması gerekmez.

Birçok GC çerçevesinde, çöp toplayıcının nesneleri veya bunların bölümlerini, bunlara ilk yazma girişiminin değiştirildikleri gerçeğini kaydetmek için özel kodu tetikleyeceği şekilde işaretlemesi mümkündür. Yeni nesnelere referanslar içerebileceğinden, oluşturulmasına bakılmaksızın değiştirilmiş bir nesne veya bunun bir bölümü bir sonraki koleksiyonda taranmalıdır. Öte yandan, koleksiyonlar arasında değiştirilmeyen birçok eski nesnenin olması çok yaygındır. Düşük nesil taramaların bu tür nesneleri görmezden gelebilmesi, bu tür taramaların aksi takdirde olduğundan daha hızlı tamamlanmasını sağlayabilir.

Btw, nesnelerin ne zaman değiştirildiğini algılayamasa ve her bir GC geçişindeki her şeyi taramak zorunda olsa bile, kuşak çöp toplama işleminin bir sıkıştırma toplayıcının "süpürme" sahne performansını iyileştirebileceğini unutmayın. Bazı gömülü ortamlarda (özellikle sıralı ve rasgele bellek erişimleri arasında hızda çok az fark olan veya hiç fark olmayanlar), bellek bloklarını hareket ettirmek etiketleme referanslarına kıyasla nispeten pahalıdır. Sonuç olarak, "markalama" aşaması yeni bir toplayıcı kullanılarak hızlandırılamasa bile, "tarama" aşamasını hızlandırmak faydalı olabilir.


bellek bloklarını hareket ettirmek herhangi bir sistemde pahalıdır, bu nedenle süpürmeyi geliştirmek dört Ghz CPU sisteminizde bile bir kazançtır.
gbjbaanb

@gbjbaanb: Birçok durumda, canlı nesneleri bulmak için her şeyi tarama maliyeti, nesneleri taşımak tamamen ücretsiz olsa bile önemli ve sakıncalı olurdu. Sonuç olarak, pratik olduğunda eski nesneleri taramaktan kaçınılmalıdır. Öte yandan, eski nesneleri sıkıştırmaktan kaçınmak, basit çerçevelerde bile gerçekleştirilebilen basit bir optimizasyondur. BTW, biri küçük bir gömülü sistem için bir GC çerçevesi tasarlıyorsa, değişmez nesneler için bildirim desteği yardımcı olabilir.
Değişken

... basitçe değişebilen nesnelerin her GC geçişinde taranması gerektiğini varsayalım ama değişmeyen nesneler buna gerek duymaz. Değişmez bir nesne inşa etmenin tek yolu değişebilir alanda bir "prototip" oluşturmak ve daha sonra onu kopyalamak olsa bile, tek bir ekstra kopyalama işlemi nesneyi gelecekteki GC işlemlerinde tarama ihtiyacını önleyebilir.
supercat

Bu arada, 1980'lerin 6502 mikroişlemciler için Microsoft'tan türetilen BASIC uygulamalarındaki (ve belki de başkalarının) çöp toplama performansı, bazı durumlarda değişmeyecek çok fazla dizge oluşturan bir program "sonraki" dize ayırma. Böyle bir değişiklik, çöp toplayıcının hala gerekli olup olmadığını görmek için eski dizelerden herhangi birini incelemesini önleyecektir. Commodore 64 pek yüksek teknolojiye sahip değildi, ama böyle bir "kuşak" GC orada bile yardımcı olacaktı.
supercat

7

Bahsettiğiniz GC'ler kuşak çöp toplayıcılarıdır. "Bebek ölüm oranı" veya "kuşak hipotezi" olarak bilinen bir gözlemden en iyi şekilde yararlanmak üzere tasarlanmıştır, bu da çoğu nesneye çok hızlı bir şekilde erişilemez hale geldiği anlamına gelir. Gerçekten de köklerden başlayarak tararlar, ancak tüm eski nesneleri görmezden gelirler . Bu nedenle, bellekteki nesnelerin çoğunu taramaları gerekmez, sadece genç nesneleri tararlar (ulaşılamayan eski nesneleri tespit etmeme pahasına, en azından bu noktada değil).

"Ama bu yanlış", çığlık attığını duyuyorum, "eski nesneler genç nesnelere atıfta bulunabilir". Haklısınız ve bunun, tüm eski nesnelerin kontrol edilmesi gereken ve göz ardı edilmesi güvenli olan, bilgi edinme etrafında hızlı ve verimli bir şekilde dönen birkaç çözüm var. Kayıt nesnelerine ya da küçük nesillere işaretçiler içeren küçük (nesnelerden daha büyük, ancak tüm yığından çok daha küçük) bellek aralıklarına kaynar. Diğerleri bunları benden daha iyi tanımladı, bu yüzden size birkaç anahtar kelime vereceğim: Kart işaretleme, hatırlanan kümeler, yazma engelleri. Başka teknikler de (melezler dahil) var, ancak bunlar farkında olduğum ortak yaklaşımları kapsıyor.


3

Hangi fidanlık nesnelerinin hala canlı olduğunu bulmak için, toplayıcının yalnızca kök kümesini ve son koleksiyondan bu yana mutasyona uğramış eski nesneleri taraması gerekir , çünkü yakın zamanda mutasyona uğratılmamış eski bir nesne muhtemelen genç bir nesneye işaret edemez . Bu bilgileri değişen hassasiyet seviyelerinde korumak için farklı algoritmalar vardır (mutasyona uğramış alanların tam bir setinden mutasyonun meydana gelebileceği bir dizi sayfaya), ancak hepsi genellikle bir tür yazma engeli içerir : her referansta çalışan kod GC'nin defter tutmayı güncelleyen tipte alan mutasyonu.


1

En eski ve en basit nesil çöp toplayıcıları aslında tüm belleği taradılar ve bunu yaparken diğer tüm işlemleri durdurmak zorunda kaldılar. Daha sonra algoritmalar bu konuda çeşitli şekillerde geliştirildi - kopyalama / taramayı arttırma veya paralel çalıştırma. Modern çöp toplayıcıların çoğu nesneleri nesillere ayırır ve kuşaklar arası göstergeleri dikkatli bir şekilde yönetir, böylece yeni nesiller eskilerini rahatsız etmeden toplanabilir.

Kilit nokta, çöp toplayıcıların derleyiciyle ve tüm belleği izlediği yanılsamasını sürdürmek için çalışma zamanının geri kalanıyla yakın işbirliği içinde çalışmasıdır.


1970'lerin sonlarından önce mini bilgisayarlar ve ana çerçevelerde hangi çöp toplama yaklaşımlarının kullanıldığından emin değilim, ancak Microsoft BASIC çöp toplayıcı, en azından 6502 makinede, "sonraki dize" işaretçisini belleğin üstüne ayarlayacak ve daha sonra arama yapacak tüm dize başvuruları, "sonraki dize işaretçisinin" altındaki en yüksek adresi bulmak için kullanılır. Bu dize "sonraki dize işaretçisi" nin hemen altına kopyalanır ve bu işaretçi hemen altına park edilir. Algoritma daha sonra tekrar eder. Kod sağlamak için işaretçiler jinx mümkün oldu ...
supercat

... kuşak koleksiyonu gibi bir şey. Bazen basitçe her neslin üst adreslerini tutarak ve her GC döngüsünden önce ve sonra birkaç işaretçi takas işlemi ekleyerek "nesil" koleksiyonu uygulamak için BASIC yama ne kadar zor olacağını merak ettim. GC performansı hala oldukça kötü olurdu, ancak çoğu durumda onlarca saniyeden on saniyeye kadar tıraş olabilir.
supercat

-2

Temel olarak ... GC, kullanımda olanı ve olmayanı ayırmak için "kovalar" kullanır. Kontrol ettikten sonra, kullanılmayan şeyleri siler ve diğer her şeyi 2. nesle (1. nesilden daha az kontrol edilir) taşır ve daha sonra hala kullanımda olan şeyleri 2. den'e 3. jenerasyona taşır.

Bu nedenle, 3. kuşaktaki şeyler genellikle bir nedenden dolayı açık kalmış nesnelerdir ve GC orayı çok sık kontrol etmez.


1
Ancak hangi nesnelerin kullanımda olduğunu nasıl bilebilir?
Pieter van Ginkel

Hangi nesnelere ulaşılabilir koddan erişilebileceğini izler. Bir nesneye artık çalıştırılabilecek herhangi bir koddan erişilemediğinde (örneğin, geri dönen bir yöntemin kodu), GC toplamanın güvenli olduğunu bilir
JohnL

Her ikiniz de GC'lerin nasıl verimli olduklarını değil, nasıl doğru olduklarını açıklıyorsunuz. Sorudan yola çıkarak OP bunu çok iyi biliyor.

@delnan evet Hangi nesnelerin kullanımda olduğunu nasıl bildikleri sorusunu cevaplıyordum, bu da Pieter'in yorumunda olan şeydi.
JohnL

-5

Bu GC tarafından genellikle kullanılan algoritma, Naïve mark-and-süpürme

Ayrıca bunun C # tarafından değil, CLR tarafından yönetildiğinin farkında olmalısınız .


Bu, Mono'nun çöp toplayıcısını okumaktan aldığım duygu. Ancak, anlamadığım şey, neden koleksiyondaki tüm çalışma setini tarıyorlarsa, GEN-0 koleksiyonunun çok hızlı bir şekilde toplandığı nesil bir koleksiyoncuya sahipler. 2GB çalışan bir setle bu nasıl hızlı olabilir?
Pieter van Ginkel

iyi, mono için gerçek GC Sgen, bu mono-project.com/Generational_GC veya bazı çevrimiçi makaleleri schani.wordpress.com/tag/mono infoq.com/news/2011/01/SGen okumalısınız , nokta şu ki CLR ve CLI gibi bu yeni teknolojiler gerçekten modüler bir tasarıma sahiptir, dil CLR için bir şey ifade etmenin bir yolu haline gelir ve ikili kod üretmenin bir yolu olmaz. Sorunuz algoritmalarla değil uygulama ayrıntılarıyla ilgili, çünkü bir algoritmanın hala bir uygulaması yok, sadece Mono'dan teknik kağıtları ve makaleleri okumalısınız, başka kimse yok.
user827992

Kafam karıştı. Bir çöp toplayıcının kullandığı strateji bir algoritma değil mi?
Pieter van Ginkel

2
-1 OP'yi karıştırmayı bırakın. GC'nin CLR'nin bir parçası olması ve dile özgü olmaması hiç alakalı değildir. Bir GC çoğunlukla yığın ortaya koymaktadır ve erişilebilirlik belirler şekilde karakterize edilir, ve ikincisi her bunun için kullanılan algoritmanın (ler) ile ilgili. Bir algoritmanın birçok uygulaması olabilir ve uygulama ayrıntılarına takılmamanız gerekirken, algoritma tek başına kaç nesnenin taranacağını belirler. Bir kuşak GC, "kuşak hipotezini" kullanmaya çalışan bir algoritma + yığın düzenidir (çoğu nesnenin genç öldüğü). Bunlar saf değil.

4
Algoritma! = Uygulama, ancak bir uygulama bunu ancak farklı bir algoritmanın uygulaması haline gelmeden önce sapabilir. GC dünyasında bir algoritma açıklaması çok spesifiktir ve kreş koleksiyonundaki tüm yığını taramamak ve kuşaklararası işaretçilerin nasıl bulunup saklandığı gibi şeyler içerir. Bir algoritmanın, algoritmanın belirli bir adımının ne kadar süreceğini size söylemediği doğrudur, ancak bu, bu soru ile hiç ilgili değildir.
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.