2d şehir kurucuda pahalı işlevler için performans nasıl artırılır


9

Zaten cevapları araştırdım ama pahalı fonksiyonları / hesaplamaları ele almak için en iyi yaklaşımı bulamadım.

Mevcut oyunumda (2B kiremit tabanlı bir şehir binası) kullanıcı binalar yerleştirebilir, yollar inşa edebilir. Bir bina bu kavşağa bağlı değilse, etkilenen binanın üzerinde bir "Yola bağlı değil" işareti açılır (aksi takdirde kaldırılması gerekir). Binaların çoğunun yarıçapı vardır ve birbirleriyle de ilişkili olabilirler (örneğin bir itfaiye, 30 kiremit yarıçapındaki tüm evlere yardımcı olabilir). Yol bağlantısı değiştiğinde de güncellemem / kontrol etmem gereken şey bu.

Dün büyük bir performans sorunuyla karşılaştım. Şu senaryoyu inceleyelim: Bir kullanıcı elbette binaları ve yolları da silebilir. Bir kullanıcı bağlantıdan hemen sonra bağlantıyı keserse , aynı anda birçok binayı güncellemem gerekiyor . İlk tavsiye biri iç içe döngüler (kesinlikle bu senaryoda büyük bir nedenidir) önlemek için olacağını düşünüyorum ama kontrol etmek zorunda ...

  1. Bir yol döşemesinin kaldırılması durumunda bir bina hala kavşağa bağlıysa (bunu sadece o yoldan etkilenen binalar için yapıyorum). (Bu senaryoda daha küçük bir sorun olabilir)
  2. yarıçap çinileri listesi ve yarıçap içinde binalar olsun (iç içe döngüler - büyük sorun!) .

    // Go through all buildings affected by erasing this road tile.
    foreach(var affectedBuilding in affectedBuildings) {
        // Get buildings within radius.
        foreach(var radiusTile in affectedBuilding.RadiusTiles) {
            // Get all buildings on Map within this radius (which is technially another foreach).
            var buildingsInRadius = TileMap.Buildings.Where(b => b.TileIndex == radiusTile.TileIndex);  
    
            // Do stuff.
        }
    }
    

Tüm bunlar FPS'imi bir saniye boyunca 60'tan neredeyse 10'a indirir.

Ben de yapabilirdim. Fikirlerim şöyle olurdu:

  • Bunun için ana iş parçacığı (Güncelleme işlevi) değil başka bir iş parçacığı kullanılmıyor. Birden çok iş parçacığı kullanmaya başladığımda kilitleme sorunları ile karşılaşabilirim.
  • Çok sayıda hesaplamayı işlemek için bir kuyruk kullanmak (bu durumda en iyi yaklaşım ne olurdu?)
  • Daha fazla hesaplama yapmaktan kaçınmak için nesnelerimde (binalarda) daha fazla bilgi tut (örneğin yarıçaptaki binalar).

Son yaklaşımı kullanarak bunun yerine bu foreach formunda bir yuva kaldırabilirsiniz:

// Go through all buildings affected by erasing this road tile.
foreach(var affectedBuilding in affectedBuildings) {
    // Go through buildings within radius.
    foreach(var buildingInRadius in affectedBuilding.BuildingsInRadius) {
        // Do stuff.
    }
}

Ama bunun yeterli olup olmadığını bilmiyorum. Cities Skylines gibi oyunlar, oyuncunun büyük bir haritası varsa çok daha fazla bina ile başa çıkmak zorundadır. Bunları nasıl ele alıyorlar ?! Tüm binalar aynı anda güncellenmediğinden bir güncelleme kuyruğu olabilir.

Fikirlerinizi ve yorumlarınızı dört gözle bekliyorum!

Çok teşekkürler!


2
Bir profil oluşturucu kullanmak, kodun hangi bitinin sorun olduğunu belirlemenize yardımcı olmalıdır. Etkilenen binaları bulma şekliniz olabilir veya belki // do şeyler olabilir. Yan not olarak büyük oyunlar City Skylines'i dörtlü ağaçlar gibi uzamsal veri yapılarını kullanarak bu sorunları ele alır, bu nedenle tüm uzamsal sorgular bir for döngüsü ile bir dizi boyunca gitmekten çok daha hızlıdır. Örneğin sizin durumunuzda, tüm binaların bağımlılık grafiğine sahip olabilirsiniz ve bu grafiği takip ederek, yinelemesiz neyi etkilediğini hemen öğrenebilirsiniz.
Exaila

Ayrıntılı bilgi için teşekkürler. Bağımlılık fikrini seviyorum! Buna bir göz atacağım!
Yheeky

Tavsiyeniz harikaydı! Ben sadece bana gösterdi, kavşak bağlantı hala geçerli olup olmadığını kontrol etmek için etkilenen her bina için bir yol bulma işlevi olduğunu gösterdi VS profiler kullandım. Tabii ki bu cehennem kadar pahalı! Sadece yaklaşık 5 FPS ama hiçbir şeyden daha iyi. Bundan kurtulacak ve yol karolarına binalar atayacağım, böylece bu yol bulma kontrolünü tekrar tekrar yapmam gerekmiyor. Çok teşekkürler! Hayır sadece binaları daha büyük olan yarıçapta düzeltmem gerekiyor.
Yheeky

Yararlı bulduğunuz için memnunum: D
Exaila

Yanıtlar:


3

Bina kapsamını önbelleğe alma

Hangi binaların bir efektör-bina aralığında (efektörden veya etkilenenden önbellekleyebileceğiniz) önbelleğe alınması fikri kesinlikle iyi bir fikirdir. Binalar (genellikle) hareket etmez, bu nedenle bu pahalı hesaplamaları yeniden yapmak için çok az neden vardır. "Bu bina neyi etkiler" ve "bu binayı neyin etkilediğini" yalnızca bir bina oluşturulduğunda veya kaldırıldığında kontrol etmeniz gereken bir şeydir.

Bu, bellek için klasik bir CPU döngüsü değişimidir.

Bölgeye göre kapsama bilgilerini işleme

Bu bilgileri takip etmek için çok fazla bellek kullandığınız ortaya çıkarsa, bu bilgileri harita bölgelerine göre işleyip işlemeyeceğinize bakın. Haritanızı n*nçinileri. Bir bölge bir itfaiye teşkilatı tarafından tamamen kaplanmışsa, o bölgedeki tüm binalar da kapsam dahilindedir. Bu nedenle kapsama bilgilerini tek tek binalara göre değil, yalnızca bölgelere göre depolamanız gerekir. Bir bölge yalnızca kısmen kapsanmışsa, o bölgedeki yan yapı bağlantılarını ele almaya geri dönmeniz gerekir. Yani binalarınız için güncelleme fonksiyonu önce "Bu binanın bulunduğu bölge bir itfaiyenin kapsadığı mı?" ve eğer değilse "Bu bina tek başına bir itfaiye tarafından kapsanıyor mu?". Bu aynı zamanda güncellemeleri de hızlandırır, çünkü bir itfaiye departmanı kaldırıldığında artık 2000 binanın kapsama durumunu güncellemenize gerek yoktur, sadece 100 binayı ve 25 bölgeyi güncellemeniz gerekir.

Gecikmeli güncelleme

Yapabileceğiniz başka bir optimizasyon, her şeyi hemen güncellememek ve her şeyi aynı anda güncellememek.

Bir binanın yol ağına hala bağlı olup olmadığı her kareyi kontrol etmeniz gereken bir şey değildir (Bu arada, özellikle grafik teorisine biraz bakarak bunu optimize etmenin bazı yollarını da bulabilirsiniz). Binalar, bina inşa edildikten sonra sadece birkaç saniyede bir periyodik olarak kontrol ederse (VE yol ağında bir değişiklik varsa) tamamen yeterli olacaktır. Aynı durum bina menzili etkileri için de geçerlidir. Bir binanın sadece birkaç yüz çerçeveyi kontrol etmesi mükemmel bir şekilde kabul edilebilir "Beni etkileyen itfaiye departmanlarından en az biri hala aktif mi?"

Böylece, güncelleme döngünüzün her güncelleme için bir seferde sadece birkaç yüz bina için bu pahalı hesaplamaları yapmasını sağlayabilirsiniz. Şu anda ekranda bulunan binalara tercih vermek isteyebilirsiniz, böylece oyuncular eylemleri için anında geri bildirim alırlar.

Çok İş Parçacığı ile İlgili

Şehir inşaatçıları, özellikle oyuncuların gerçekten büyük bir yapı oluşturmasına izin vermek istiyorsanız ve yüksek bir simülasyon karmaşıklığına sahip olmak istiyorsanız, hesaplama açısından daha pahalı tarafta olma eğilimindedir. Bu yüzden uzun vadede oyununuzdaki hangi hesaplamaların eşzamansız olarak ele alınabileceğini düşünmek yanlış olmayabilir.


Bu, SNES'deki SimCity'nin gücün yeniden bağlanması için neden biraz zaman aldığını açıklıyor, sanırım bu alandaki diğer etkileriyle de oluyor.
lozzajp

Yararlı yorumunuz için teşekkürler! Ayrıca hafızada daha fazla bilgi tutmanın oyunumu hızlandırabileceğini düşünüyorum. TileMap'i bölgelere bölme fikrini de seviyorum, ancak bu yaklaşımın uzun süredir ilk sorunumdan kurtulmak için yeterince iyi olup olmadığını bilmiyorum. Gecikmeli güncelleme ile ilgili bir sorum var. Varsayalım ki FPS'imi 60'dan 45'e düşüren bir fonksiyonum var.
Yheeky

@Yheeky Bunun evrensel olarak uygulanabilir bir çözümü yoktur, çünkü hangi hesaplamaları geciktirebileceğiniz, hangisini yapamayacağınız ve mantıklı bir hesaplama birimi oldukça duruma bağlıdır.
Philipp

Bu hesaplamaları ertelemeye çalıştığım yol, "CurrentlyUpdating" bayrağı olan öğelerle bir kuyruk oluşturmaktı. Yalnızca bu bayrağın true değerine ayarlandığı bu öğe işlendi. Hesaplama tamamlandığında öğe listeden çıkarıldı ve sonraki öğe işlendi. Bu işe yaramalı, değil mi? Ancak, bir hesaplamanın kendisinin FPS'nizi düşüreceğini biliyorsanız ne tür bir yöntem kullanılabilir?
Yheeky

1
@Yheeky Dediğim gibi, evrensel olarak uygulanabilir bir çözüm yok. Ne genellikle denemek istiyorsunuz (bu sırayla): 1. Daha uygun algoritmalar ve / veya veri yapıları kullanarak bu hesaplamayı optimize edip edemeyeceğinize bakın. 2. Bireysel olarak geciktirebileceğiniz alt görevlere bölüp bölemeyeceğinize bakın. 3. Bunu ayrı bir tehdit içinde yapıp yapamayacağınızı görün. 4. Bu hesaplamaya ihtiyaç duyan oyun-mekanikten kurtulun ve hesaplamayı daha ucuz bir şeyle değiştirip değiştiremeyeceğinizi görün.
Philipp

3

1. Yinelenen çalışma .

Sizin affectedBuildingsfarklı yarıçapları üst üste böylece, muhtemelen birbirine yakın bulunmaktadır. Güncellenmesi gereken binaları işaretleyin ve güncelleyin.

var toBeUpdated = new HashSet<Tiles>();
foreach(var affectedBuilding in affectedBuildings) {
    foreach(var radiusTile in affectedBuilding.RadiusTiles) {
         toBeUpdated.Add(radiusTile);

}
foreach (var tile in toBeUpdated)
{
    var buildingsInTile = TileMap.Buildings.Where(b => b.TileIndex == radiusTile.TileIndex);
    // Do stuff.
}

2. Uygun Olmayan Veri Yapıları.

var buildingsInTile = TileMap.Buildings.Where(b => b.TileIndex == radiusTile.TileIndex);

açıkça olmalı

var buildingsInRadius = tile.Buildings;

Binaların IEnumerablesürekli yineleme süresine sahip olduğu (ör. a List<Building>)


İyi bir nokta! Sanırım MoreLINQ kullanarak bir Distinct () kullanarak denedim ama bu kopyaları kontrol daha hızlı olabilir kabul ediyorum.
Yheeky
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.