Bir varlık bileşen sistemi oyun motorundaki CPU önbelleğinden nasıl faydalanılır?


15

CPU önbelleğini akıllıca kullanmak için iyi bir mimari olan ECS oyun motoru belgelerinde sık sık okurum.

Ama cpu önbelleğinden nasıl faydalanabileceğimizi anlayamıyorum.

Bileşenler bir diziye (veya bir havuza), bitişik belleğe kaydedilirse, yalnızca bileşenleri sırayla okursak cpu önbelleği kullanmak için iyi bir yoldur.

Sistemleri kullandığımızda, belirli tipte bileşenlere sahip varlıkların listesi olan varlık listesine ihtiyaç duyarlar.

Ancak bu listeler, bileşenleri sıralı olarak değil rastgele bir şekilde verir.

Peki, önbellek vuruşunu en üst düzeye çıkarmak için bir ECS nasıl tasarlanır?

DÜZENLE :

Örneğin, bir Fizik sistemi, RigidBody ve Transform bileşenlerine sahip varlık için bir varlık listesine ihtiyaç duyar (RigidBody için bir havuz ve Transform bileşenleri için bir havuz vardır).

Varlıkları güncelleme döngüsü şu şekilde olacaktır:

for (Entity eid in entitiesList) {
    // Get rigid body component
    RigidBody *rigidBody = entityManager.getComponentFromEntity<RigidBody>(eid);

    // Get transform component
    Transform *transform = entityManager.getComponentFromEntity<Transform>(eid);

    // Do something with rigid body and transform component
}

Sorun, varlık1'in RigidBody bileşeninin havuzunun indeksinde 2 ve havuzunun 0 dizininde varlık1'in Tranform bileşeninde olabilmesidir (çünkü bazı varlıklar diğerine değil bazı bileşenlere sahip olabilir ve varlık ekleme / silme nedeniyle / bileşenler rastgele).

Bileşenler bellekte bitişik olsa bile, rastgele okunurlar ve bu yüzden daha fazla önbellek özlemi olur, değil mi?

Döngüdeki sonraki bileşenleri önceden getirmenin bir yolu yoksa?


her bir bileşeni nasıl tahsis ettiğinizi gösterebilir misiniz?
concept3d

Basit bir havuz ayırıcısı ve havuzdaki bileşenlerin yer değiştirmesini yönetmek için bileşen referansına sahip olan bir Tutamaç yöneticisi ile (bileşenleri bellekte bitişik tutmak için).
Johnmph

Örnek döngünüz, bileşen güncellemelerinin varlık başına serpiştirildiğini varsayar. Birçok durumda bileşenleri bileşen türüne göre toplu olarak güncellemek mümkündür (örn. Önce tüm sert gövde bileşenlerini güncelleyin, ardından tüm dönüşümleri bitmiş sert gövde verileriyle güncelleyin, ardından tüm oluşturma verilerini yeni dönüşümlerle güncelleyin ...) - bu önbelleği iyileştirebilir her bileşen güncellemesi için kullanın. Bu tür yapıların Nick Wiggill'in aşağıda önerdiğini düşünüyorum.
DMGregory

Bu benim kötü olan örneğim, aslında, Fizik sisteminden daha çok "tamamlanmış katı cisim verileriyle tüm dönüşümleri güncelle" sistemi. Ancak sorun aynı kalıyor, bu sistemlerde (katı gövdeli güncelleme dönüşümü, dönüştürmeyle güncelleme oluşturma, ...), aynı anda birden fazla bileşene sahip olmamız gerekecek.
Johnmph

Bunun da ilgili olup olmadığından emin değil misiniz? gamasutra.com/view/feature/6345/…
DMGregory

Yanıtlar:


13

Mick West'in makalesinde varlık bileşeni verilerinin doğrusallaştırılması süreci tam olarak açıklanmaktadır. Tony Hawk serisinde yıllar önce, performansı büyük ölçüde artırmak için bugünkünden çok daha az etkileyici bir donanımda çalıştı. Temel olarak her farklı varlık verisi türü için (konum, puan ve ne olursa olsun) global, önceden tahsis edilmiş diziler kullandı ve her diziye sistem çapında update()işlevinin farklı bir aşamasında referans verdi . Her bir varlık için verilerin bu global dizilerin her birinde aynı dizi dizininde olacağını varsayabilirsiniz, örneğin, eğer oynatıcı ilk önce oluşturulmuşsa, verilerinin [0]her dizide olabileceğini varsayabilirsiniz .

Önbellek optimizasyonuna daha da spesifik olan Christer Ericsson , C ve C ++ için slaytlar oluşturuyor.

Biraz daha fazla ayrıntı vermek için, her bir veri türü için (örneğin, konum, xy ve z gibi) her bir veri türü için bitişik bellek bloklarını (en kolay olarak diziler olarak) kullanmayı denemelisiniz, bu tür her veri bloğunu farklı şekilde kullanarak update()belirli bir update()çağrıda yeniden kullanmak istediğiniz herhangi bir veriyi yeniden kullanmadan önce, önbelleğin donanımın LRU algoritması yoluyla temizlenmediğinden emin olmak için Bahsettiğiniz gibi, yapmak istemediğiniz şey, varlıkların ve bileşenlerinizin üzerinden ayrı nesneler olarak tahsis etmektir new, çünkü her bir varlık örneğindeki farklı türlerdeki veriler serpiştirilecek ve referans yerini azaltacaktır.

Bileşenler (veriler) arasında, ilişkili verilerden (örneğin Transform + Physics, Transform + Renderer) bazı verilerin kesinlikle bulunamayacağı şekilde bağımlılıklar varsa, Transform verilerini hem Fizik hem de Oluşturucu dizilerinde çoğaltmayı tercih edebilirsiniz. , ilgili tüm verilerin performans açısından kritik her işlem için önbellek çizgisi genişliğine uyduğundan emin olun.

Ayrıca, L2 ve L3 önbelleğinin (bunları hedef platformunuz için varsayabilirseniz), L1 önbelleğinin kısıtlayıcı bir çizgi genişliği gibi sorunlarının hafifletilmesi için çok şey yaptığını unutmayın. Bu nedenle, bir L1 özünde bile, bunlar genellikle ana belleğe ek bilgileri önleyecek güvenlik ağlarıdır; bu, herhangi bir önbellek düzeyindeki ek bilgilerden daha yavaş büyüklük düzenidir.

Veri yazmayla ilgili not Yazma ana belleğe çağrı yapmaz. Varsayılan olarak, bugünün sistemlerinde geri yazma önbelleği etkin durumdadır: bir değer yazmak, onu ana belleğe değil, yalnızca önbelleğe (başlangıçta) yazar, bu nedenle tıkanıklık olmaz. Yalnızca ana bellekten veri istendiğinde (önbelledeyken gerçekleşmez) ve bayat olduğunda, ana bellek önbellekten güncellenir.


1
C ++ için yeni olabilecek herkes için sadece bir not: std::vectortemel olarak dinamik olarak yeniden boyutlandırılabilir bir dizidir ve bu nedenle de bitişiktir (eski C ++ sürümlerinde fiili ve yeni C ++ sürümlerinde de jure). Bazı uygulamaları std::dequeda "yeterince bitişiktir" (Microsoft değil).
Sean Middleditch

2
@Johnmph Oldukça basit: Referans konumunuz yoksa, hiçbir şeyiniz yoktur. İki veri parçası yakından ilişkili ise (mekansal ve fizik bilgileri gibi), yani bunlar birlikte işlendiyse, bunları araya eklenmiş tek bir bileşen olarak sıkıştırmanız gerekebilir. Ama sonra aklında bulunsun başka mantık (diyelim ki, AI) mekansal veriler daha sonra mekansal verilerin bir sonucu olarak zarar görebileceği konusunda leverages değil yanında eklenmesini o buna . Bu yüzden en çok performansı gerektiren şeylere bağlıdır (belki de sizin durumunuzdaki fizik). bu mantıklı mı?
Mühendis

1
@Johnmph evet, Nick ile tamamen katılıyorum, bellekte nasıl saklandıklarıyla ilgili, eğer bellekte çok uzak olmayan iki bileşene işaret eden bir varlık varsa, önbellek satırına sığmaları gerekiyor.
concept3d

2
@Johnmph: Gerçekten de, Mick West'in makalesi minimum bağımlılıklar olduğunu varsayar. Yani: Bağımlılıkları en aza indirin; Örneğin bu bağımlılıkları en aza edemez önbellek çizgisinde Çoğalt veri ... yanında Transform içerir hem rijit cisim ve Render; ve önbellek hatlarını sığdırmak için veri atomlarınızı mümkün olduğunca azaltmanız gerekebilir ... bu kısmen ondalık nokta değeri başına kayan noktadan sabit noktaya (4 bayt - 2 bayt) gidilerek sağlanabilir. Ancak şu ya da bu şekilde, nasıl yaparsanız yapın, verilerinizin maksimum performans için concept3d belirtildiği gibi önbellek çizgisi genişliğine sığması gerekir.
Mühendis

2
@Johnmph. Hayır. Transform verilerini her yazdığınızda, her iki diziye de yazmanız yeterlidir. Endişelenmen gereken yazarlar değil. Bir kez bir yazı gönderdikten sonra, bu kadar iyi. Daha sonra güncellemede, Fizik ve İşleyici'yi çalıştırdığınızda, ilgili tüm verilere derhal, CPU'ya yakın ve kişisel olarak tek bir önbellek satırında erişmesi gereken okumalar . Ayrıca, gerçekten hepsine birlikte ihtiyacınız varsa, ya daha fazla çoğaltma yaparsınız ya da fiziğin, tek bir önbellek hattına sığmasını ve dönüştürülmesini sağlarsınız ... 64 bayt yaygındır ve aslında oldukça fazla veri! ...
Mühendis
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.