Neden Büyük Nesne Yığını ve neden önemsiyoruz?


107

Generations ve Large nesne yığını hakkında okudum. Ama yine de Büyük nesne yığınına sahip olmanın önemi (veya faydası) ne olduğunu anlayamıyorum?

CLR, büyük nesneleri depolamak için 2. Nesil'e (Gen0 ve Gen1 için eşiğin Büyük nesneleri işlemek için küçük olduğu düşünüldüğünde) güvenmiş olsaydı (performans veya bellek açısından) ne ters gidebilirdi?


6
Bu bana .NET tasarımcıları için iki soru soruyor: 1. Neden OutOfMemoryException atılmadan önce LOH birleştirme çağrılmıyor? 2. Neden LOH nesnelerinin bir arada kalma eğilimi yok (büyükler yığının sonunu tercih eder ve başlangıçta küçüktür)
Jacob Brewer

Yanıtlar:


196

Bir çöp toplama sadece referans verilmeyen nesnelerden kurtulmakla kalmaz, aynı zamanda yığını da sıkıştırır . Bu çok önemli bir optimizasyon. Sadece bellek kullanımını daha verimli hale getirmekle kalmaz (kullanılmayan delikler yok), CPU önbelleğini çok daha verimli hale getirir. Önbellek, modern işlemciler için gerçekten çok önemli, bellek veri yolundan daha hızlı, kolay bir büyüklük sıralaması.

Sıkıştırma, baytları kopyalayarak yapılır. Ancak bu zaman alır. Nesne ne kadar büyükse, onu kopyalama maliyetinin olası CPU önbellek kullanım iyileştirmelerinden daha ağır basma olasılığı o kadar yüksektir.

Bu yüzden başa baş noktasını belirlemek için bir dizi kıyaslama yaptılar. Ve kopyalamanın artık performansı iyileştirmediği kesme noktası olarak 85.000 bayta ulaştı. İkili diziler için özel bir istisna ile, dizi 1000'den fazla öğe içerdiğinde bunlar 'büyük' ​​olarak kabul edilir. Bu, 32 bit kod için başka bir optimizasyondur, büyük nesne yığın ayırıcısı, yalnızca 4'e hizalı ayıran normal nesil ayırıcıdan farklı olarak, 8'e hizalanmış adreslerde bellek ayırma özelliğine sahiptir. Bu hizalama, çift için büyük bir olaydır yanlış hizalanmış bir çift okumak veya yazmak çok pahalıdır. İşin garibi, seyrek Microsoft bilgisi asla uzun dizilerden bahsetmiyor, bunun ne olduğundan emin değil.

Fwiw, büyük nesne yığınının sıkıştırılmaması konusunda çok sayıda programcı endişesi var. Bu, mevcut tüm adres alanının yarısından fazlasını kullanan programlar yazdıklarında her zaman tetiklenir. Ardından, kullanılmayan çok sayıda sanal bellek olmasına rağmen programın neden bombalandığını öğrenmek için bellek profili oluşturucu gibi bir araç kullanılır. Böyle bir araç, daha önce büyük bir nesnenin yaşadığı ancak çöpün toplandığı kullanılmayan bellek parçalarını, LOH'daki delikleri gösterir. Bu, LOH'nin kaçınılmaz fiyatıdır, delik yalnızca boyutu eşit veya daha küçük olan bir nesne için bir tahsis tarafından yeniden kullanılabilir. Gerçek sorun, bir programın herhangi bir zamanda tüm sanal belleği kullanmasına izin verilmesi gerektiğini varsaymaktır .

Aksi halde kodu 64 bit işletim sisteminde çalıştırarak tamamen ortadan kalkan bir sorun. 64 bitlik bir işlem, 8 terabaytlık sanal bellek adres alanına sahiptir, 32 bitlik bir işlemden 3 büyüklük sırası daha fazladır. Sadece deliklerden kaçamazsın.

Uzun lafın kısası, LOH kodun daha verimli çalışmasını sağlar. Mevcut sanal bellek adres alanını kullanma pahasına daha az verimli.


UPDATE, .NET 4.5.1 artık LOH, GCSettings.LargeObjectHeapCompactionMode özelliğini sıkıştırmayı destekliyor . Sonuçlarına dikkat edin lütfen.


3
@Hans Passant, x64 sistemi hakkında bir açıklama yapar mısınız, yani bu problem tamamen ortadan kalkıyor mu?
Johnny_D

LOH'nin bazı uygulama detayları mantıklı ama bazıları beni şaşırtıyor. Örneğin, eğer birçok büyük nesne oluşturulur ve terk edilirse, Gen0 koleksiyonlarında parça parça yerine onları bir Gen2 koleksiyonunda toplu olarak silmenin genellikle istenebileceğini anlayabiliyorum, ancak biri oluşturup terk ederse örneğin 22.000 dizeden oluşan bir dizi hiçbir dış referans yoktur, Gen0 ve Gen1 koleksiyonlarının diziye herhangi bir referans olup olmadığına bakılmaksızın 22.000 dizgenin tümünü "canlı" olarak etiketlemesinin ne avantajı vardır?
supercat

6
Elbette parçalanma sorunu x64'te de aynı. Başlamadan önce sunucu işleminizi çalıştırmak yalnızca birkaç gün daha sürecektir.
Lothar

1
Hmm, hayır, asla 3 büyüklük derecesini küçümseme. Çöp 4 terabaytlık bir yığın toplamanın ne kadar sürdüğü, ona yaklaşmadan çok önce keşfetmekten kaçınamayacağınız bir şeydir.
Hans Passant

2
@HansPassant Lütfen, şu ifadeyi biraz daha detaylandırır mısınız: "4 terabaytlık bir yığın toplamanın ne kadar sürdüğü, ona yaklaşmadan çok önce keşfetmekten kaçınamayacağınız bir şeydir."
nispeten_random

9

Nesnenin boyutu bazı sabitlenmiş değerlerden (.NET 1'de 85000 bayt) büyükse, CLR onu Büyük Nesne Yığını'na koyar. Bu optimize eder:

  1. Nesne tahsisi (küçük nesneler büyük nesnelerle karıştırılmaz)
  2. Çöp toplama (LOH yalnızca tam GC'de toplanır)
  3. Bellek birleştirme (LOH asla nadiren sıkıştırılmaz)

9

Küçük Nesne Yığını (SOH) ve Büyük Nesne Yığını (LOH) arasındaki temel fark, bu makalede gösterildiği gibi, SOH'daki belleğin toplandığında sıkıştırılması ve LOH'nin sıkıştırılmamasıdır . Büyük nesnelerin sıkıştırılması çok maliyetlidir. Makaledeki örneklere benzer şekilde, diyelim ki bellekteki bir baytı taşımak için 2 döngü gerekiyor, ardından 2GHz'lik bir bilgisayarda 8 MB'lık bir nesneyi sıkıştırmak için 8ms gerekiyor, bu da büyük bir maliyet. Büyük nesnelerin (çoğu durumda diziler) pratikte oldukça yaygın olduğu düşünüldüğünde, sanırım Microsoft'un bellekte büyük nesneleri iğnelemesinin ve LOH önermesinin nedeni budur.

BTW, bu gönderiye göre , LOH genellikle bellek parçası sorunları oluşturmaz.


1
Yönetilen nesnelere büyük miktarlarda veri yüklemek, genellikle LOH'yi sıkıştırmanın 8 ms'lik maliyetini gölgede bırakır. Uygulamada çoğu büyük veri uygulamasında, LOH maliyeti, uygulama performansının geri kalanının yanında önemsizdir.
Shiv

3

Prensip, bir sürecin çok sayıda kısa ömürlü büyük nesneler yaratmasının olası olmaması (ve muhtemelen kötü tasarım) olmasıdır, bu nedenle CLR, büyük nesneleri, GC'yi normal yığından farklı bir zamanlamayla çalıştırdığı ayrı bir yığına tahsis eder. http://msdn.microsoft.com/en-us/magazine/cc534993.aspx


Ayrıca, örneğin 2. neslin üzerine büyük nesneler koymak, performansa zarar verebilir, çünkü özellikle küçük bir miktar serbest bırakılırsa ve BÜYÜK nesneler yeni bir yere kopyalanmak zorunda kalırsa, hafızayı sıkıştırmak uzun zaman alırdı. Mevcut LOH, performans nedenleriyle sıkıştırılmamıştır.
Christopher Currens

Bence bu sadece kötü bir tasarım çünkü GC bunu iyi idare edemiyor.
CodesInChaos

@CodeInChaos Görünüşe göre, .NET
Christian.K

1
@CodeInChaos: Sistemin, kısa ömürlü LOH nesnelerinden bile bellek almaya çalışmadan önce gen2 koleksiyonuna kadar beklemesi mantıklı olsa da, LOH nesnelerini (ve tuttukları nesneleri) bildirmek için herhangi bir performans avantajı göremiyorum. referanslar) gen0 ve gen1 koleksiyonları sırasında koşulsuz canlı. Böyle bir varsayımla mümkün kılınan bazı optimizasyonlar var mı?
supercat

@supercat Myles McDonnell tarafından bahsedilen bağlantıya baktım. Anladığım kadarıyla: 1. LOH koleksiyonu bir gen 2 GC'de gerçekleşir. 2. LOH koleksiyonu, sıkıştırmayı içermez (makale yazıldığında). Bunun yerine, ölü nesneleri yeniden kullanılabilir olarak işaretleyecek ve bu delikler, yeterince büyükse gelecekteki LOH tahsislerine hizmet edecektir. Nokta 1 nedeniyle, gen 2'de çok sayıda nesne varsa bir gen 2 GC'nin yavaş olacağını düşünürsek, bu durumda LOH kullanmaktan mümkün olduğunca kaçınmanın daha iyi olacağını düşünüyorum.
robbie fan

0

CLR konusunda uzman değilim, ancak büyük nesneler için ayrılmış bir yığına sahip olmanın, mevcut nesil yığınlarının gereksiz GC taramalarını önleyebileceğini hayal ediyorum. Büyük bir nesnenin tahsis edilmesi, önemli miktarda bitişik boş bellek gerektirir. Nesil yığınlarındaki dağınık "deliklerden" bunu sağlamak için, sık sık sıkıştırmalara ihtiyacınız olacaktır (bunlar yalnızca GC döngüleri ile yapılır).

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.