Unity ile oyun kurarken GC'yi nasıl hesaba katmalıyım?


15

* Bildiğim kadarıyla, iOS için Unity3D, Mono çalışma zamanına dayanıyor ve Mono'nun sadece nesil mark & ​​sweep GC'si var.

Bu GC sistemi, oyun sistemini durduran GC zamanından kaçınamaz. Örnek havuzlaması bunu azaltabilir, ancak tamamen değil, çünkü CLR'nin temel sınıf kitaplığında örneklemenin gerçekleşmesini kontrol edemeyiz. Bu gizli küçük ve sık vakalar nihayetinde belirleyici olmayan GC süresini artıracaktır. Tam GC'yi periyodik olarak zorlamak performansı büyük ölçüde düşürür (Mono aslında GC'yi tamamlayabilir mi?)

Peki, Unity3D'yi büyük performans düşüşü olmadan kullanırken bu GC süresinden nasıl kaçınabilirim?


3
En son Unity Pro Profiler, belirli işlev çağrılarında ne kadar çöp üretildiğini içerir. Bu aracı, aksi halde hemen belirgin olmayabilecek çöp oluşturabilecek diğer yerleri görmenize yardımcı olması için kullanabilirsiniz.
Tetrad

Yanıtlar:


18

Neyse ki, işaret ettiğiniz gibi, COMPACT Mono yapıları nesilsel bir GC kullanır (sadece düz bir liste tutan WinMo / WinPhone / XBox gibi Microsoft'ların aksine).

Oyununuz basitse GC iyi bir şekilde ele almalıdır, ancak işte bakmak isteyebileceğiniz bazı işaretçiler.

Erken Optimizasyon

İlk olarak, düzeltmeye çalışmadan önce bunun sizin için bir sorun olduğundan emin olun.

Havuzlama Pahalı Referans Türleri

Sık oluşturduğunuz veya derin yapıları olan referans türlerini bir araya getirmelisiniz. Her birine örnek olarak şunlar verilebilir:

StackHavuzunuz olarak a kullanmalısınız (a kullanan çoğu uygulamanın aksine Queue). Bunun nedeni, Stackbir nesneyi havuza iade ederseniz ve başka bir şey hemen yakalarsa; etkin bir sayfada olma şansınız daha yüksek, hatta şanslıysanız CPU önbelleğinde bile. Sadece biraz daha hızlı. Ayrıca, havuzlarınızı her zaman boyut olarak sınırlayın (sınırınız aşıldıysa 'checkin'leri dikkate almayın).

Temizlemek için Yeni Listeler Oluşturmaktan Kaçının

ListGerçekten demek istediğinizde yeni bir şey yaratmayın Clear(). Arka uç dizisini yeniden kullanabilir ve bir dizi dizi ayırma ve kopyasını kaydedebilirsiniz. Buna benzer olarak, anlamlı bir başlangıç ​​kapasitesine sahip listeler oluşturun ve oluşturun (unutmayın, bu bir sınır değildir - sadece bir başlangıç ​​kapasitesi) - doğru olması gerekmez, sadece bir tahmin. Bu, a dışında herhangi bir toplama türü için geçerli olmalıdır LinkedList.

Mümkün Olduğunda Yapı Dizilerini (veya Listelerini) Kullanın

Nesnelerin arasında dolaşırsanız kullanım yapılarından (veya genel olarak değer türlerinden) çok az kazanç elde edersiniz. Örneğin, çoğu 'iyi' parçacık sisteminde ayrı ayrı parçacıklar büyük bir dizide saklanır: dizi ve indeks parçacığın kendisi yerine geçirilir. Bunun çok iyi çalışmasının nedeni, GC'nin diziyi toplaması gerektiğinde içeriği tamamen atlayabilmesidir (bu ilkel bir dizidir - burada yapacak bir şey yoktur). Yani 10.000 nesneye bakmak yerine GC sadece 1 diziye bakmalıdır: büyük kazanç! Yine, bu sadece değer türleriyle çalışacaktır .

RoyT'tan sonra. bazı daha uygulanabilir ve yapıcı geri bildirim sağladım. Bu tekniği sadece büyük miktarlarda varlıklarla (binlerce ila onbinlerce) uğraşırken kullanmalısınız. Buna ek olarak bu olmalıdır bir yapı olması gerekir herhangi bir referans tipi alanlara sahip ve bir açık-daktilo dizide yaşamalıdır. Geri bildirimlerinin aksine, onu bir sınıftaki bir alan olan bir diziye yerleştiriyoruz - yani yığına inecek (bir yığın tahsisinden kaçınmaya çalışmıyoruz - sadece GC çalışmasından kaçınıyoruz). Gerçekten önemsiyoruz, GC'nin bir O(1)işlem yerine bir işlemde kolayca bakabileceği birçok değer içeren bitişik bir bellek yığını O(n).

GC bu parçaları hareket ettirmeye çalışırken parçalanma veya aşırı çalışma olasılığını azaltmak için bu dizileri uygulama başlangıcınıza mümkün olduğunca yakın olarak ayırmalısınız (ve yerleşik tür yerine karma bağlantılı bir liste kullanmayı düşünmelisiniz) List).

GC.Collect ()

Bu kesinlikle kendinizi nesiller boyu bir GC ile ayağa vurmanın en iyi yoludur (bakınız: "Performans Konuları"). Sadece EXTREME miktarda çöp oluşturduğunuzda çağırmalısınız - ve bunun bir sorun olabileceği tek örnek, içeriği bir seviye için yükledikten hemen sonradır - ve o zaman bile sadece ilk nesli toplamalısınız ( ) Umarım üçüncü nesile nesnelerin tanıtımını engeller.GC.Collect(0);

IDisposable ve Field Nulling

Artık bir nesneye ihtiyacınız olmadığında alanları boş bırakmaya değer (kısıtlanmış nesnelerde daha fazla). Bunun nedeni, GC'nin nasıl çalıştığının ayrıntılarındadır: geçerli koleksiyonda kaldırılan diğer nesneler nedeniyle bu nesnenin kökleri kaldırılmış olsa bile yalnızca köklenmemiş (yani başvuruda bulunmayan) nesneleri kaldırır ( not: bu, GC'ye bağlıdır kullanılan lezzet - bazıları aslında zincirleri temizler). Ek olarak, bir nesne bir koleksiyonda hayatta kalırsa hemen bir sonraki nesle yükseltilir - bu, toplama sırasında tarlalarda kalan nesnelerin tanıtılacağı anlamına gelir. Her ardışık jenerasyonun toplanması katlanarak daha pahalıdır (ve nadiren meydana gelir).

Aşağıdaki örneği alın:

MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// G1 Collection
MyNestObject (G2) -> MyFurtherNestedObject (G2)
// G2 Collection
MyFurtherNestedObject (G3)

Eğer MyFurtherNestedObjectyanlışlıkla G3 onu terfi çünkü - çok megabayt nesne içeren GC oldukça uzun bir süre bakmak olmaz garanti edilebilir. Bunu bu örnekle karşılaştırın:

MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// Dispose
MyObject (G1)
MyNestedObject (G1)
MyFurtherNestedObject (G1)
// G1 Collection

Disposer kalıbı, nesnelerin özel alanlarını temizlemelerini istemek için öngörülebilir bir yol ayarlamanıza yardımcı olur. Örneğin:

public class MyClass : IDisposable
{
    private MyNestedType _nested;

    // A finalizer is only needed IF YOU CONTROL UNMANAGED RESOURCES
    // ~MyClass() { }

    public void Dispose()
    {
       _nested = null;
    }
}

1
WTF hakkında ne düşünüyorsun? Windows'daki .NET Framework kesinlikle nesildir. Sınıflarının GetGeneration yöntemleri bile var .
DeadMG

2
Windows üzerinde .NET bir Nesil Çöp Toplayıcı kullanır, ancak WP7 ve Xbox360'ta kullanılan .NET Compact Framework, muhtemelen düz bir listeden daha fazlası olsa da, tam nesil bir liste değildir.
Roy T.

Jonathan Dickinson: Yapıların her zaman cevap olmadığını eklemek isterim. Net ve muhtemelen de Mono'da bir yapı yığının üzerinde (ki bu iyi) yaşamak zorunda değildir, aynı zamanda öbek üzerinde de yaşayabilir (ki bu bir Çöp Toplayıcısına ihtiyacınız olan yer). Bunun için kurallar oldukça karmaşıktır, ancak genel olarak bir yapı değer içermeyen türler içerdiğinde gerçekleşir.
Roy T.

1
@RoyT. yukarıdaki yoruma bakınız. Yapıların bir parçacık sistemi gibi bir dizideki büyük miktarda veri için iyi olduğunu belirttim. Bir dizideki GC yapıları konusunda endişeleriniz varsa gidilecek yol; parçacıklar gibi veriler için.
Jonathan Dickinson

10
@DeadMG ayrıca, WTF'nin cevabı gerçekten de yanlıştır - cevabı doğru okumadığınız düşünülürse. Lütfen öfkenizi sakinleştirin ve bunun gibi genel olarak iyi davranılmış bir toplulukta nefret dolu yorumlar yazmadan önce cevabı tekrar okuyun.
Jonathan Dickinson

7

Gereksinim duyduğunuz bir şey çağırmadıkça çok az dinamik örnekleme temel kitaplığın içinde otomatik olarak gerçekleşir. Bellek ayırma ve yeniden yerleştirmenin büyük çoğunluğu kendi kodunuzdan gelir ve bunu kontrol edebilirsiniz.

Anladığım kadarıyla, bundan tamamen kaçınamazsınız - yapabileceğiniz tek şey, mümkün olan yerlerde nesneleri geri dönüştürüp havuzlamak, Sınıflar yerine yapıları kullanmak, UnityGUI sisteminden kaçınmak ve genellikle dinamik bellekte yeni nesneler yapmaktan kaçınmaktır. performans önemli olduğunda.

Ayrıca çöp toplama işlemini belirli zamanlarda zorlayabilirsiniz, bu da yardımcı olabilir veya etmeyebilir: buradaki bazı önerilere bakın: http://unity3d.com/support/documentation/Manual/iphone-Optimizing-Scripts.html


2
GC'yi zorlamanın etkilerini gerçekten açıklamalısınız. GC buluşsal yöntemlerine ve düzenine zarar verir ve yanlış kullanılırsa daha büyük bellek sorunlarına yol açabilir: XNA / MVP'ler / personel topluluğu genellikle yükleme sonrası içeriği bir toplama zorlamak için yalnızca makul bir süre olarak kabul eder. Konuya sadece bir cümle ayırırsanız, bundan tamamen bahsetmekten gerçekten kaçınmalısınız. GC.Collect()= şimdi boş hafıza ama daha sonra büyük olasılıkla sorunlar.
Jonathan Dickinson

Hayır, GC'yi zorlamanın sonuçlarını açıklamalısınız çünkü benden daha fazla şey biliyorsunuz. :) Ancak, Mono GC'nin Microsoft GC ile aynı olmadığını ve risklerin aynı olmayabileceğini unutmayın.
Kylotan

Yine de +1. Ben devam etti ve ilginç bulabilirsiniz GC ile bazı kişisel deneyim üzerinde ayrıntılı.
Jonathan Dickinson
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.