Varlık / Bileşen sistemlerindeki varlıkların aynı anda işlenmesi için Okuma / Hesaplama / Yazma adımlarını etkili bir şekilde ayırma


11

Kurmak

Varlıklar (davranışları olmayan saf veri olan) bir dizi özniteliklere sahip olabilen bir varlık bileşeni mimarisine sahibim ve bu veriler üzerinde hareket eden varlık mantığını çalıştıran sistemler var. Temel olarak, biraz sahte kodda:

Entity
{
    id;
    map<id_type, Attribute> attributes;
}

System
{
    update();
    vector<Entity> entities;
}

Tüm varlıklar boyunca sabit bir oranda hareket eden bir sistem,

MovementSystem extends System
{
   update()
   {
      for each entity in entities
        position = entity.attributes["position"];
        position += vec3(1,1,1);
   }
}

Aslında, update () 'i olabildiğince verimli bir şekilde paralelleştirmeye çalışıyorum. Bu, tüm sistemleri paralel olarak çalıştırarak veya bir sistemin her güncellemesine () birkaç bileşen vererek farklı iş parçacıklarının aynı sistemin güncellemesini yürütebilmesi için, ancak bu sisteme kayıtlı farklı bir varlık alt kümesi için yapılabilir.

Sorun

Gösterilen MovementSystem durumunda, paralelleştirme önemsizdir. Varlıklar birbirine bağlı olmadığından ve paylaşılan verileri değiştirmediğinden, tüm varlıkları paralel olarak taşıyabiliriz.

Bununla birlikte, bu sistemler bazen varlıkların birbirleriyle, bazen aynı sistem içinde, ancak çoğu zaman birbirine bağlı farklı sistemler arasında etkileşim kurmasını (veri okuma / yazma) gerektirir.

Örneğin, bir fizik sisteminde bazen varlıklar birbirleriyle etkileşime girebilir. İki nesne çarpışır, konumları, hızları ve diğer nitelikleri onlardan okunur, güncellenir ve daha sonra güncellenen özellikler her iki varlığa geri yazılır.

Ve motordaki oluşturma sistemi varlıkları oluşturmaya başlamadan önce, ilgili tüm özniteliklerin olması gereken şey olduğundan emin olmak için diğer sistemlerin yürütmeyi tamamlamasını beklemek zorundadır.

Bunu körü körüne paralel hale getirmeye çalışırsak, farklı sistemlerin verileri aynı anda okuyabileceği ve değiştirebileceği klasik yarış koşullarına yol açacaktır.

İdeal olarak, tüm sistemlerin aynı verileri değiştiren diğer sistemler hakkında endişelenmeksizin ve programcıya, bu sistemleri manuel olarak (bazen mümkün bile olmayabilir).

Temel bir uygulamada, bu sadece kritik bölümlere tüm verileri okuyup yazarak (mutekslerle korunarak) sağlanabilir. Ancak bu, büyük miktarda çalışma zamanı yükünü indükler ve muhtemelen performansa duyarlı uygulamalar için uygun değildir.

Çözüm?

Benim düşünceme göre, olası bir çözüm, verilerin okunması / güncellenmesi ve yazılmasının ayrıldığı bir sistem olacaktır, böylece tek bir pahalı aşamada, sistemler sadece verileri okur ve hesaplamak için gerekeni hesaplar, bir şekilde sonuçları önbelleğe alır ve sonra hepsini yazar değiştirilen veri ayrı bir yazma işleminde hedef varlıklara geri döndürülür. Tüm sistemler, çerçevenin başında olduğu durumda ve daha sonra çerçevenin bitiminden önce, tüm sistemler güncelleştirmeyi bitirdiğinde, önbelleğe alınan tüm farklı sonuçların bulunduğu bir seri yazma geçişi gerçekleşir. sistemler tekrarlanır ve hedef varlıklara geri yazılır.

Bu, sonuçta önbelleğe almanın ve yazma geçişinin maliyetini (hem çalışma zamanı performansı hem de kod ek yükü açısından) aşacak kadar büyük olabileceği (belki de yanlış?) Fikrine dayanır.

Soru

Optimum performans elde etmek için böyle bir sistem nasıl uygulanabilir? Böyle bir sistemin uygulama ayrıntıları nelerdir ve bu çözümü kullanmak isteyen bir Varlık Bileşen sisteminin önkoşulları nelerdir?

Yanıtlar:


1

----- (gözden geçirilmiş soruya dayanarak)

İlk nokta: sürüm derleme çalışma sürenizi profilli olduğundan ve özel bir ihtiyaç bulduğunuzdan bahsetmediğiniz için, bunu en kısa sürede yapmanızı öneririm. Profiliniz neye benziyor, önbellekleri kötü bellek düzeniyle çöpe atıyor musunuz, bir çekirdek% 100 oranında sabitlenmiş, ECS'nizi motorunuzun geri kalanına karşı işlemek için ne kadar göreceli zaman harcanıyor vb.

Bir varlıktan okuyun ve bir şey hesaplayın ... ve daha sonraya kadar ara depolama alanında bir yerde sonuçları tutun? Okuma + hesaplama + mağazayı düşündüğünüz şekilde ayırabileceğinizi ve bu ara mağazanın saf yükten başka bir şey olmasını beklediğinizi sanmıyorum.

Ayrıca, sürekli işleme yaptığınız için takip etmek istediğiniz ana kural CPU çekirdeği başına bir iş parçacığına sahip olmaktır. Sanırım buna yanlış katmana bakıyorsunuz, tek tek varlıklara değil tüm sistemlere bakmayı deneyin.

Sistemlerinizin arasında bir bağımlılık grafiği oluşturun; bu sistemin daha önceki bir sistemin çalışmasından elde edilmesi gereken sistemdir. Bu bağımlılık ağacına sahip olduğunuzda, tüm sistemleri bir iş parçacığında işlemek için varlıklarla dolu tüm sistemlere kolayca gönderebilirsiniz .

Diyelim ki bağımlılık ağacınız, bir tasarım meselesi olan bramble'ların ve ayı tuzaklarının tacizidir. Buradaki en iyi durum, her bir sistemin içinde her bir varlığın o sistem içindeki başka bir sonuca bağlı olmamasıdır. Burada, bu sistemin sahip olduğu iki çekirdekli ve 200 varlıklı bir örnek için, işlemeyi 0-99 ve 100-199 arasındaki iş parçacıkları arasında kolayca alt bölümlere ayırabilirsiniz.

Her iki durumda da, her aşamada bir sonraki aşamanın bağlı olduğu sonuçları beklemeniz gerekir. Ancak bu sorun değil, çünkü toplu olarak işlenen on büyük veri bloğunun sonuçlarını beklemek, küçük bloklar için bin kez senkronize etmekten çok daha üstündür.

Bir bağımlılık grafiği oluşturmanın arkasındaki fikir, "Diğer sistemleri paralel olarak çalışacak şekilde bulma ve birleştirme" gibi görünen imkansız görevi otomatikleştirerek önemsizleştirmekti. Böyle bir grafik, önceki sonuçları sürekli olarak bekleyerek engellendiğine dair işaretler gösteriyorsa, bir okuma + değiştirme ve gecikmeli yazma oluşturmak yalnızca blokajı taşır ve işlemenin seri niteliğini kaldırmaz.

Ve seri işleme sadece her bir dizi noktası arasında paralel olarak döndürülebilir, ancak toplamda değil. Ama bunu anlıyorsunuz çünkü bu sizin sorunun özü Henüz yazılmamış verilerden okuma önbelleği alsanız bile, kullanılabilir olması için bu önbelleği beklemeniz gerekir.

Paralel mimariler oluşturmak bu tür kısıtlamalarla kolay ve hatta mümkün olsaydı, bilgisayar bilimi Bletchley Park'tan bu yana sorunla mücadele etmeyecekti.

Tek gerçek çözüm , sıra noktalarını mümkün olduğunca nadiren gerekli kılmak için tüm bu bağımlılıkları en aza indirmek olacaktır . Bu, alt sistemlerin , her bir alt sistem içinde, dişlerle paralel gitmenin önemsiz hale geldiği sıralı işlem adımlarına bölünmesini içerebilir .

Bu sorun için en iyisi var ve gerçekten bir tuğla duvara başınızı vurmak acıyorsa, o zaman daha küçük tuğla duvarlara kırın, böylece sadece inciklerinizi vurmanızı tavsiye etmekten başka bir şey değildir.


Size söylediğim için üzgünüm, ama bu cevap verimsiz görünüyor. Bana sadece aradığım şeyin mevcut olmadığını söylüyorsun, bu mantıksal olarak yanlış görünüyor (en azından prensipte) ve ayrıca insanların daha önce birkaç yerde böyle bir sisteme başvurduklarını gördüm (hiç kimse yeterince vermez) Bununla birlikte, bu soruyu sormak için ana motivasyon olan detaylar). Her ne kadar, orijinal sorumda neredeyse yeterince ayrıntılı olmamam mümkün olabilir, bu yüzden onu kapsamlı bir şekilde güncelledim (ve zihnim bir şeyle karşılaşırsa güncellemeye devam edeceğim).
TravisG

Ayrıca suç da amaçlanmadı: P
TravisG

@TravisG Patrick'in işaret ettiği gibi genellikle diğer sistemlere bağımlı sistemler vardır. Kare gecikmelerinden kaçınmak veya bir mantık adımının bir parçası olarak birden fazla güncelleme geçişini önlemek için, kabul edilen çözüm güncelleme aşamasını serileştirmek, alt sistemleri mümkün olduğunca paralel çalıştırarak, alt sistemleri tüm bağımlılıklarla serileştirmek parallel_for () kavramı kullanan alt sistem. Alt sistem güncelleme geçişi gereksinimlerinin herhangi bir kombinasyonu için idealdir ve en esnektir.
Naros

0

Bu soruna ilginç bir çözüm duydum: Fikir, varlık verilerinin 2 kopyasının olması gerektiğidir (savurgan, biliyorum). Bir kopya mevcut kopya, diğeri önceki kopya olacaktır. Mevcut kopya kesinlikle yazılır ve geçmiş kopya kesinlikle okunur. Sistemlerin aynı veri öğelerine yazmak istemediğini varsayıyorum, ancak durum böyle değilse, bu sistemler aynı iş parçacığında olmalıdır. Her bir iş parçacığı, verilerin birbirini dışlayan bölümlerinin mevcut kopyalarına yazma erişimine sahip olacak ve her iş parçacığı, verilerin tüm geçmiş kopyalarına okuma erişimine sahip olacak ve böylece mevcut kopyaları geçmiş kopyalardaki verileri kullanarak güncelleyebilir. kilitleme. Her kare arasında, mevcut kopya geçmiş kopya olur, ancak rollerin değişimini yapmak istersiniz.

Bu yöntem aynı zamanda yarış koşullarını da ortadan kaldırır çünkü tüm sistemler, sistem işlenmeden önce / sonra değişmeyecek eski bir durumla çalışacaktır.


Bu John Carmack'in yığın kopya hilesi, değil mi? Ben merak ettim, ama yine de potansiyel olarak birden fazla iş parçacığı aynı çıkış konuma yazabilirsiniz aynı sorunu vardır. Her şeyi "tek geçişli" tutarsanız, muhtemelen iyi bir çözümdür, ancak bunun ne kadar mümkün olduğundan emin değilim.
TravisG

Ekran görüntüleme gecikmesine giriş, GUI reaktivitesi dahil olmak üzere 1 kare kadar artacaktır. Hangi eylem / zamanlama oyunları veya RTS gibi ağır GUI manipülasyonları için önemli olabilir. Ancak bunu yaratıcı bir fikir olarak seviyorum.
Patrick Hughes

Bunu bir arkadaşımdan duydum ve bunun bir Carmack hilesi olduğunu bilmiyordum. Oluşturma işleminin nasıl yapıldığına bağlı olarak, bileşenlerin oluşturulması bir çerçevenin arkasında olabilir. Bunu sadece Güncelleme aşaması için kullanabilir, ardından her şey güncel olduğunda geçerli kopyadan oluşturabilirsiniz.
John McDonald

0

Verilerin paralel işlenmesini sağlayan 3 yazılım tasarımı biliyorum:

  1. Verileri sırayla işleyin : Verileri birden çok iş parçacığı kullanarak işlemek istediğimiz için bu garip gelebilir. Bununla birlikte, çoğu senaryo, işin tamamlanması için diğer iş parçacıklarının beklemesi veya uzun süre çalışmasını gerçekleştirmesi için birden çok iş parçacığı gerektirir. En yaygın kullanım, kullanıcı arabirimini tek bir iş parçacığında güncelleyen UI iş parçacıklarıdır, diğer iş parçacıkları arka planda çalışabilir, ancak doğrudan UI öğelerine erişmelerine izin verilmez. Arka plan parçacıklarından sonuç iletmek için, bir sonraki makul fırsatta tek iş parçacığı tarafından işlenecek iş kuyrukları kullanılır.
  2. Veri erişimini senkronize edin: bu, aynı verilere erişen birden çok iş parçacığını işlemenin en yaygın yoludur. Çoğu programlama dili, verilerin aynı anda birden çok iş parçacığı tarafından okunduğu ve / veya yazıldığı bölümleri kilitlemek için sınıflar ve araçlara sahiptir . Ancak, operasyonları engellememeye dikkat edilmelidir. Öte yandan, bu yaklaşımın gerçek zamanlı uygulamalarda çok fazla maliyeti vardır.
  3. Kulp eşzamanlı değişiklikler gerçekleşmeden sadece: Bu iyimser yaklaşım yapılabilir çarpışmalar nadiren meydana gelirse. Birden fazla erişim yoksa veriler okunacak ve değiştirilecektir, ancak verilerin aynı anda ne zaman güncellendiğini algılayan bir mekanizma vardır. Bu durumda, tek hesaplama başarıya kadar tekrar yürütülür.

Bir varlık sisteminde kullanılabilecek her yaklaşım için bazı örnekler:

  1. Bir CollisionSystemokur Positionve RigidBodybileşenleri düşünelim ve güncelleme gerekir a Velocity. VelocityDoğrudan manipülasyon CollisionSystemyerine , irade yerine CollisionEventbir EventSystem. Bu olay daha sonra, diğer güncellemelerle sırayla işlenir Velocity.
  2. A EntitySystem, okuması ve yazması gereken bir dizi bileşeni tanımlar. Her biri Entityiçin okumak istediği her bileşen için bir okuma kilidi ve güncellemek istediği her bileşen için bir yazma kilidi sağlayacaktır. Bu şekilde, EntitySystemgüncelleme işlemleri senkronize edilirken her biri bileşenleri aynı anda okuyabilecektir.
  3. Örnek alarak MovementSystem, Positionbileşenidir değişmez ve içeren revizyon numarası. MovementSystemSavely okur Positionve Velocitykomponent ve yeni hesaplar Positionokuma arttırılarak revizyon numarası ve güncellenmesi denemeden Positionbileşeni. Eşzamanlı bir değişiklik olması durumunda, çerçeve bunu güncellemede gösterir ve Entitytarafından güncellenmesi gereken varlıklar listesine geri konulur MovementSystem.

Sistemlere, varlıklara ve güncelleme aralıklarına bağlı olarak, her yaklaşım iyi veya kötü olabilir. Bir varlık sistemi çerçevesi, kullanıcının performansı değiştirmek için bu seçenekler arasında seçim yapmasına izin verebilir.

Umarım tartışmaya bazı fikirler ekleyebilirim ve bu konuda bazı haberler olup olmadığını lütfen bana bildirin.

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.