Bir O (N ^ 2) işlevini geliştirme (diğer tüm varlıklara göre yinelenen tüm varlıklar)


21

Biraz arka plan, C ++ 'ta bir arkadaşımla bir evrim oyununu varlık sistemi için ENTT kullanarak kodluyorum. Yaratıklar 2B bir harita üzerinde dolaşıyor, yeşillikleri ve diğer canlıları yiyor, ürüyorlar ve özellikleri değişiyor.

Ayrıca, oyun gerçek zamanlı olarak çalıştırıldığında performans iyi (60fps sorun yok), ancak önemli değişiklikleri görmek için 4 saat beklemek zorunda kalmamak için önemli ölçüde hızlandırabilmek istiyorum. Bu yüzden mümkün olduğunca hızlı almak istiyorum.

Yaratıkların yiyeceklerini bulmaları için etkili bir yöntem bulmakta zorlanıyorum. Her yaratığın, kendilerine yeterince yakın olan en iyi yemeği araması gerekir.

Oyunun örnek ekran görüntüsü

Yemek yemek istiyorsa, merkezde gösterilen yaratığın 149.64 yarıçapında (görüş mesafesi) kendi etrafına bakması ve hangi yiyeceğe bakması gerektiğini, hangisinin beslenmeye, mesafeye ve türüne (et veya bitki) dayandığına karar vermesi beklenir. .

Her canlının yiyeceklerini bulmaktan sorumlu olan fonksiyon, çalışma zamanının% 70'ini yiyor. Şu anda nasıl yazıldığını basitleştirerek, şöyle bir şeye gider:

for (creature : all_creatures)
{
  for (food : all_entities_with_food_value)
  {
    // if the food is within the creatures view and it's
    // the best food found yet, it becomes the best food
  }
  // set the best food as the target for creature
  // make the creature chase it (change its state)
}

Bu işlev, yiyecek arayan ve durum değiştirene kadar, yiyecek arayan her yaratığın her bir kene çalıştırılır. Ayrıca, herkesin kendilerine sunduğu en iyi yemeğin peşinden gitmesini sağlamak için, yeni yiyecekler zaten belli bir yiyeceği kovalayan yaratıklar için ortaya çıkar.

Bu sürecin nasıl daha verimli hale getirileceğine dair fikirlere açığım. Karmaşıklığı O(N-2) den düşürmeyi çok isterdim , ama bunun mümkün olup olmadığını bile bilmiyorum.

Zaten geliştirdiğim bir yöntem all_entities_with_food_valuegrubu, bir yaratığın yiyeceği yemek için çok büyük bir yineleme yaptığında durduracak şekilde gruplamasıdır. Diğer iyileştirmeler memnuniyetle karşılanır!

EDIT: Cevaplarınız için hepinize teşekkür ederim! Çeşitli cevaplardan çeşitli şeyler uyguladım:

Öncelikle ve basitçe yaptım ki suçluluk fonksiyonu her beş tene sadece bir kez koşuyordu, bu da oyunun 4x etrafında daha hızlı olmasına neden oldu;

Ondan sonra yiyecek arama sisteminde sakladığım yiyecekle aynı kene içinde bulunan yiyecekleri içeren bir dizi oluşturdum. Bu şekilde, yaratığın koyduğu yiyecekleri sadece ortaya çıkan yeni yiyeceklerle karşılaştırmam gerekiyor.

Son olarak, alanın bölünmesi ve BVH ve dörtlünün göz önünde bulundurulması üzerine araştırma yaptıktan sonra, sonuncusuna gittim, sanki davama çok daha basit ve daha uygun olduğunu hissediyorum. Oldukça hızlı bir şekilde uyguluyorum ve performansı oldukça artırdım, yiyecek arama neredeyse hiç zaman alıyor!

Şimdi render yapmak benim yavaşlatan şey, ama bu başka bir gün için bir sorun. Hepinize teşekkür ederim!


2
Aynı anda çalışan birden fazla CPU çekirdeğinde birden fazla iş parçacığı denediniz mi?
Ed Marty,

6
Ortalama kaç yaratığınız var? Anlık görüntüden yargılamak o kadar yüksek görünmüyor. Bu her zaman böyle olursa, alan bölümlendirme çok yardımcı olmaz. Dikkatinizden kaçmış değil , her kene de bu işlevi çalıştıran? Her seferinde 10 tane tik kolabilirsin. Simülasyonun sonuçları niteliksel olarak değişmemelidir.
Turms

4
Yiyecek değerlendirmesinin en pahalı kısmını bulmak için ayrıntılı bir profil belirlediniz mi? Genel karmaşıklığa bakmak yerine, belki de sizi boğan belirli bir hesaplama veya hafıza yapısı erişimi olup olmadığını görmeniz gerekir.
Harabeck

Saf bir öneri: Şimdi yaptığınız O (N ^ 2) yerine bir dörtlü veya ilgili veri yapısını kullanabilirsiniz.
Seiyria,

3
@Harabeck'in önerdiği gibi, bu süre zarfında döngünün neresinde harcandığını görmek için daha derine inerdim. Örneğin mesafe için karekök hesaplamaları yapıyorsa, kalanları pahalı bir kare yapmak zorunda kalmadan önce birçok adayı ön elemeye almak için ilk önce XY kodlarını karşılaştırabilirsiniz. if (food.x>creature.x+149.64 or food.x<creature.x-149.64) continue;Yeterli performans göstermesi durumunda, "karmaşık" bir depolama yapısı uygulamaktan daha kolay eklenmesi gerekir. (İlgili: İçsel döngünüzdeki kodun biraz daha fazlasını gönderirseniz bize yardımcı olabilir)
AC

Yanıtlar:


34

Bunu çarpışmalar olarak kavramsallaştırmadığınızı biliyorum, ancak yaptığınız şey yaratığın merkezindeki bir daireyi tüm yiyeceklerle çarpıştırmak.

Gerçekten uzaktaki olduğunu bildiğiniz yiyecekleri kontrol etmek istemiyorsunuz, sadece yakınlarda. Çarpışma optimizasyonu için genel tavsiye budur. Çarpışmaları optimize etmek için teknikler aramaya teşvik ediyorum ve arama yaparken kendinizi C ++ ile sınırlı tutmuyorum.


Yaratık yiyecek bulmak

Senaryon için dünyayı bir şebekeye koymanızı öneririm. Hücreleri en azından çarpışmak istediğiniz dairelerin yarıçapına getirin. Daha sonra yaratığın bulunduğu hücreyi ve sekize kadar komşusunu seçebilir ve yalnızca dokuz hücreye kadar olanları arayabilirsiniz.

Not : Daha küçük hücreler oluşturabilirsiniz; bu, aradığınız dairenin, oradaki yinelemeyi gerektiren göçmen komşuların ötesine uzanacağı anlamına gelir. Bununla birlikte, eğer sorun çok fazla yiyecek olması durumunda, daha küçük hücreler toplamda daha az yiyecek varlığını yinelemek anlamına gelebilir, ki bu bir noktada sizin lehinize dönüşür. Bunun olduğundan şüpheleniyorsanız, test edin.

Yiyecek hareket etmiyorsa, yaratma sırasında yiyecek varlıklarını ızgaraya ekleyebilirsiniz, böylece hücrede hangi varlıkların olduğunu aramanıza gerek kalmaz. Bunun yerine hücreyi sorgularsınız ve listede bulunur.

Hücrelerin boyutunu iki kişilik bir güç yaparsanız, yaratığın bulunduğu hücreyi koordinatlarını keserek bulabilirsiniz.

En yakını bulmak için kareye yakın mesafeyle (aka sqrt yapmayın) çalışabilirsiniz. Daha az sqrt işlemi daha hızlı işlem demektir.


Yeni yemek eklendi

Yeni yiyecekler eklendiğinde, yalnızca yakındaki canlıların uyandırılması gerekir. Aynı fikir, şu andaki hücrelerin içindeki canlıların listesini almanız gerekiyor.

Çok daha ilginç, eğer yaratığın içine koyduğu yiyeceklerden ne kadar uzakta olduğunu açıklarsanız ... doğrudan o mesafeye karşı kontrol edebilirsiniz.

Size yardımcı olacak başka bir şey, yiyeceğin onu yarattığı şeylerin farkında olmaktır. Bu, henüz yeni yemiş bir parça yiyecek peşinde koşan tüm canlılar için yiyecek bulma kodunu çalıştırmanıza izin verecektir.

Aslında, simülasyonu yiyecek olmadan başlatın ve yaratıkların açıklıklı bir sonsuzluk mesafesi vardır. Sonra yemek eklemeye başlayın. Yaratıklar hareket ettikçe mesafeleri güncelleyin ... Yiyecekler yenildiğinde, onu takip eden canlıların listesini alın ve sonra yeni bir hedef bulun. Bu durumun yanı sıra, yiyecek eklendiğinde diğer tüm güncellemeler ele alınır.


Simülasyon atlanıyor

Bir yaratığın hızını bilerek, hedefine ulaşana kadar ne kadar olduğunu bilirsiniz. Tüm yaratıklar aynı hıza sahipse, ilk ulaşacak olanı en küçük açıklamalı mesafeye sahip olandır.

Daha fazla yemek ekleyene kadar zamanını da biliyorsan ... Ve umarım üreme ve ölüm için benzer bir öngörülebilirlik vardır sonraki etkinliğe kadar geçen süreyi bilirsiniz (ya eklenmiş yiyecek ya da yaratık yeme).

O ana atla. Etrafta dolaşan yaratıkları taklit etmenize gerek yok.


1
"ve sadece orada arama yap." ve hücreler hemen komşular - toplamda 9 hücre anlamına gelir. Neden 9 Çünkü yaratığın bir hücrenin köşesinde olması durumunda.
UKMonkey

1
@UKMonkey "Hücrelerin yarıçapı ve yaratığın köşede olması durumunda" Hücreleri en azından çarpıştırmak istediğiniz dairelerin yarıçapı yapın ", sanırım sadece bu durumda dördü aramanız gerekiyor. Bununla birlikte, hücreleri daha küçük hale getirebiliriz, eğer çok fazla yiyecek ve çok az canlı varsa yararlı olabilir. Düzenleme: Ben netleşeceğim.
Theraot

2
Tabii - eğer fazladan hücrelerde araştırma yapmanız gerekiyorsa çalışmak istiyorsanız ... ama çoğu hücrede yiyecek olmazsa (verilen görüntüden); 9 hücreyi aramak, aramanız gereken 4 dosyayı bulmaktan daha hızlı olacaktır.
UKMonkey

@UKMonkey bu yüzden başlangıçta bundan bahsetmedim.
Theraot

16

Karmaşıklığı azaltmak için BVH gibi bir uzay bölümleme algoritması benimsemelisiniz . Davanıza özgü olmak için, yiyecek parçaları içeren eksen hizalı sınırlama kutularından oluşan bir ağaç yapmalısınız.

Bir hiyerarşi oluşturmak için yiyecek parçalarını AABB'lerde birbirine yaklaştırın, daha sonra bu AABB'leri aralarındaki mesafeye göre daha büyük AABB'lere yerleştirin. Bir kök düğümünüz olana kadar bunu yapın.

Ağacı kullanmak için, önce bir kök düğümüne karşı bir daire-AABB kesişme testi yapın, ardından çarpışma olursa, her ardışık düğümün çocuklarına karşı test edin. Sonunda bir grup yiyecek parçasına sahip olmalısınız.

Ayrıca yararlanabilirler AABB.cc kütüphanesine.


1
Bu gerçekten de karmaşıklığı N log N'ye indirecekti, fakat aynı zamanda bölümlendirmeyi de yapması pahalı olacaktı. Her kene bölümlemesini yapmam gerekecek gibi görünmek (yaratıklar her kene hareket ettiğinden) buna değer mi? Daha az sıklıkla bölümlere yardımcı olacak çözümler var mı?
Alexandre Rodrigues,

3
@AlexandreRodrigues tüm ağacı her kene yeniden yapmak zorunda değilsiniz, sadece hareket eden parçaları güncellemelisiniz ve sadece belirli bir AABB kabının dışına çıktığında. Performansı daha da artırmak için, düğümleri yağlamak isteyebilirsiniz (çocuklar arasında bir miktar boşluk bırakarak), böylece tüm dalı bir yaprak güncellemesinde yeniden inşa etmeniz gerekmez.
Ocelot

6
Bir BVH'nin burada çok karmaşık olabileceğini düşünüyorum - karma tablo olarak uygulanan tek tip bir ızgara yeterince iyi.
Steven

1
@Steven BVH uygulayarak gelecekte simülasyonun ölçeğini kolayca genişletebilirsiniz. Ve eğer küçük çaplı bir simülasyon için de yaparsanız, gerçekten hiçbir şey kaybetmezsiniz.
Ocelot

2

Gerçekten açıklanan uzay bölme yöntemleri, zamanınızı azaltabilirken, asıl sorun yalnızca arama değil. Yaptığın arama hacmi, görevini yavaşlatıyor. Böylece iç çevrimi en iyi duruma getirebilirsiniz ancak dış çevrimi de optimize edebilirsiniz.

Senin derdin, yoklama verilerini tutman. Sanki arka koltukta çocuklar için binde bir kez soran "henüz var mıyız" diye sormak biraz, o sırada sürücünün ne zaman orada olduğunuzu bilmesini sağlamaya gerek yok.

Bunun yerine, eğer mümkünse, her bir eylemin tamamlanmasını çözmek için onu bir kuyruğa koymak ve bu balon olaylarını açığa çıkarmak için çaba sarf etmelisiniz, bu kuyrukta değişiklikler yapabilir ancak bu tamamdır. Buna ayrık olay simülasyonu denir. Simülasyonunuzu bu şekilde uygulayabiliyorsanız, daha iyi alan bölümü aramasından elde edeceğiniz hıza göre daha ağır basan oldukça büyük bir hızlandırma arayışı içinde olursunuz.

Daha önceki bir kariyerdeki noktanın altını çizmek için fabrika simülatörleri yaptım. Bu yöntemle, her bir madde seviyesinde bütün malzeme akışını haftalarca büyük fabrikalar / havaalanları simüle ettik. Timestep tabanlı simülasyon gerçek zamana göre sadece 4-5 kat daha hızlı simülasyon yapabiliyordu.

Ayrıca gerçekten alçak bir asma meyvesi olarak çizim rutinlerinizi simülasyonunuzdan ayırmayı düşünün. Simülasyonunuz basit olsa da, hala bazı çizim ek yükleri var. Daha da kötüsü, ekran sürücüsü sizi saniyede x güncelleme ile sınırlıyor olabilir, gerçekte işlemcileriniz 100'lerce kat daha hızlı şeyler yapabilir. Bu, profil oluşturma ihtiyacını giderir.


@Traot, çizim işlerinin nasıl yapılandırıldığını bilmiyoruz. Ama yine de yeterince hızlı bir şekilde çekince bir kez çekmeceler darboğazlara dönüşecek
joojaa

1

Karmaşıklığı Nlog (N) seviyesine düşürmek için bir tarama çizgisi algoritması kullanabilirsiniz. Teori, bir yaratığı çevreleyen alanın o yaratığa diğerlerinden daha yakın olan tüm noktalardan oluşan bölgelere bölünmesini sağlayan Voronoi diyagramlarıdır.

Fortune'un algoritması sizin için Nlog (N) 'de yapıyor ve bunun üzerinde bulunan sözde kodu içeren wiki sayfası. Dışarıda kütüphane uygulamaları olduğundan da eminim. https://en.wikipedia.org/wiki/Fortune%27s_algorithm


GDSE'ye hoş geldiniz ve cevap için teşekkürler. Bunu OP'nin durumuna tam olarak nasıl uygularsınız? Sorun açıklaması, işletmenin tüm yiyecekleri görüş mesafesi dahilinde göz önüne alması ve en iyisini seçmesi gerektiğini söylüyor. Geleneksel bir Voronoi, başka bir işletmeye daha yakın olan menzilli yiyecekleri dışlar. Bir Voronoi'nin işe yaramayacağını söylemiyorum, ancak açıklamanızdan OP'nin tanımlandığı gibi problem için birinden nasıl faydalanması gerektiği açık değil.
Pikalek

Bu fikri sevdim, genişlemesini görmek isterim. Voronoi şemasını nasıl temsil ediyorsunuz (bellek veri yapısında olduğu gibi)? Nasıl sorgularsın?
Theraot,

@Sadece voronoi diyagramına ihtiyacınız yok, sadece aynı sweepline fikrini hazırlayın.
joojaa

-2

En kolay çözüm, bir fizik motorunu entegre etmek ve yalnızca çarpışma tespit algoritmasını kullanmak olabilir. Sadece her varlığın etrafına bir daire / küre oluşturun ve fizik motorunun çarpışmaları hesaplamasına izin verin. 2D için Box2D veya Chipmunk ve 3D için Bullet'i öneririm .

Bütün bir fizik motorunu entegre etmenin çok fazla olduğunu düşünüyorsanız, belirli çarpışma algoritmalarına bakmanızı öneririm. Çarpışma algılama kütüphanelerinin çoğu iki adımda çalışır:

  • Geniş faz tespiti: Bu aşamada amaç, mümkün olan en kısa sürede çarpışabilecek aday çiftlerinin listesini almaktır. İki ortak seçenek:
    • Süpürme ve kuru erik : sınırlayıcı kutuları X ekseni boyunca sıralayın ve kesişen nesneler çiftini işaretleyin. Diğer her eksen için tekrarlayın. Bir aday çifti bütün testleri geçerse, bir sonraki aşamaya geçer. Bu algoritma zamansal tutarlılığı kullanmakta çok iyidir: sıralanan varlıkların listelerini tutabilir ve her karede güncelleyebilirsiniz, ancak neredeyse sıralandıkları için çok hızlı olacaktır. Aynı zamanda mekansal tutarlılıktan da yararlanır: varlıklar artan mekansal düzende sıralandığından, çarpışmaları kontrol ederken, varlıklar çarpışmayacağı anda durdurabilirsiniz, çünkü sonrakiler daha ileride olacaktır.
    • Quadtrees, octrees ve grid gibi mekansal bölümleme veri yapıları. Izgaraların uygulanması kolaydır, ancak varlık yoğunluğu düşükse ve sınırlandırılmamış alan için uygulanması çok zorsa, çok israf olabilir. Statik uzamsal ağaçların da uygulanması kolaydır, ancak yerlerinde dengelenmesi veya güncellenmesi zordur, bu nedenle her çerçeveyi yeniden yapmanız gerekecektir.
  • Dar faz: Geniş fazda bulunan aday çiftleri daha kesin algoritmalarla daha da test edilir.
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.