Çok oklu 2D yerçekimi hesaplamaları


24

Bir uzay keşif oyunu inşa ediyorum ve şu anda yerçekimi üzerinde çalışmaya başladım (XNA ile C # da).

Yerçekimi hala ince ayarlamaya ihtiyaç duyuyor, ancak bunu yapmadan önce fizik hesaplamalarımla bazı performans konularına değinmem gerekiyor.

Bu, 100 nesneyi kullanıyor, normalde 1000'i fizik hesaplaması olmadan yapıyor, 300 FPS'den (FPS kapağımın) çok iyi alıyor, ancak 10'dan fazla nesne, oyunu (ve çalıştığı tek iş parçacığını) kendine getiriyor. Fizik hesaplamaları yaparken diz çöküyor.

İplik kullanımımı kontrol ettim ve ilk iş parçacığı tüm işten kendini öldürüyordu, bu yüzden başka bir iş parçacığı üzerinde fizik hesaplaması yapmam gerektiğini düşündüm. Ancak Gravity.cs sınıfının Update yöntemini başka bir iş parçacığında çalıştırmaya çalıştığımda, Gravity'nin Güncelleme yöntemi içinde hiçbir şey olmasa da, oyun hala 2 FPS'ye düştü.

Gravity.cs

public void Update()
    {
        foreach (KeyValuePair<string, Entity> e in entityEngine.Entities)
        {
            Vector2 Force = new Vector2();

            foreach (KeyValuePair<string, Entity> e2 in entityEngine.Entities)
            {
                if (e2.Key != e.Key)
                {
                    float distance = Vector2.Distance(entityEngine.Entities[e.Key].Position, entityEngine.Entities[e2.Key].Position);
                    if (distance > (entityEngine.Entities[e.Key].Texture.Width / 2 + entityEngine.Entities[e2.Key].Texture.Width / 2))
                    {
                        double angle = Math.Atan2(entityEngine.Entities[e2.Key].Position.Y - entityEngine.Entities[e.Key].Position.Y, entityEngine.Entities[e2.Key].Position.X - entityEngine.Entities[e.Key].Position.X);

                        float mult = 0.1f *
                            (entityEngine.Entities[e.Key].Mass * entityEngine.Entities[e2.Key].Mass) / distance * distance;

                        Vector2 VecForce = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
                        VecForce.Normalize();

                        Force = Vector2.Add(Force, VecForce * mult);
                    }
                }
            }

            entityEngine.Entities[e.Key].Position += Force;
        }

    }

Evet biliyorum. İç içe geçmiş bir foreach döngüsü, ancak yerçekimi hesaplamasını başka nasıl yapacağımı bilmiyorum ve bu işe yarıyor, kendi dişine ihtiyacı olacak kadar yoğun. (Birisi bu hesaplamaları yapmanın süper etkili bir yolunu bilse bile, hala bunun yerine nasıl birden fazla iş parçacığında yapabileceğimi bilmek istiyorum.)

EntityEngine.cs (Gravity.cs örneğini yönetir)

public class EntityEngine
{
    public Dictionary<string, Entity> Entities = new Dictionary<string, Entity>();
    public Gravity gravity;
    private Thread T;


    public EntityEngine()
    {
        gravity = new Gravity(this);
    }


    public void Update()
    {
        foreach (KeyValuePair<string, Entity> e in Entities)
        {
            Entities[e.Key].Update();
        }

        T = new Thread(new ThreadStart(gravity.Update));
        T.IsBackground = true;
        T.Start();
    }

}

EntityEngine, Game1.cs içinde oluşturulur ve Update () yöntemi Game1.cs içinde çağrılır.

Oyunun her güncellemesinde ayrı bir iş parçacığında çalıştırmak için Gravity.cs'deki fizik hesaplamama ihtiyacım var, böylece hesaplama oyunu yavaş yavaş düşürecek kadar düşük (0-2) FPS.

Bu iş parçacığı iş yapmak hakkında nasıl giderim? (Geliştirilmiş bir Planet Gravity sistemi için herhangi bir öneriniz varsa, bunlara sahipse memnuniyetle karşılanır)

Ayrıca iş parçacığını neden kullanmamam gerektiği ya da yanlış kullanmanın tehlikeleri hakkında bir ders aramıyorum, nasıl yapılacağına dair kesin bir cevap arıyorum. Zaten anladığım veya yardımcı olduğum ufak sonuçlarla bu soruyu googling yaparak bir saat geçirdim. Kaba çıkmayı kastetmiyorum, ama her zaman düz anlamlı bir cevap almak için bir programlama noob olarak zor gözüküyor, genellikle çok karmaşık bir cevap almayı tercih ederim; Birisi neden yapmak istediğimi yapmamam gerektiğini söyleyen ve alternatif sunamayan biri (yardımcı olur).

Yardım için teşekkürler!

EDIT : Aldığım cevapları okuduktan sonra, sizin gerçekten umursadığınızı ve işe yarayabilecek bir cevabı dağıtmaya çalışmadığınızı görüyorum. İki taşı bir taşla öldürmek istedim (performansı arttırmak ve çok yönlülüğün temellerini öğrenmek), ancak sorunumun çoğunun hesaplamalarımda yattığı ve diş açmanın performans artışları için değerinden daha zor olduğu anlaşılıyor. Hepinize teşekkür ederim, cevaplarınızı tekrar okuyacağım ve okulum bittiğinde çözümlerinizi deneyeceğim, Tekrar teşekkürler!


[Yukarıda belirtilen güncelleme iş parçacığı sisteminiz] şimdi ne yapıyor (çalışıyor)? BTW en kısa sürede oyun döngüsünde başlayacağım - örneğin varlıklar güncellenmeden önce.
Sabah

2
İç içe geçmiş döngülerinizin içindeki Trig çağrıları muhtemelen en büyük isabet. Onları ortadan kaldırmanın bir yolunu bulabilirseniz k, bu O(n^2)problemi çok azaltacaktır .
RBarryYoung

1
Aslında, trig çağrıları tamamen gereksizdir : önce bir vektörden bir açı hesaplarsınız, daha sonra verilen yöne işaret eden başka bir vektör üretmek için bunu kullanın. O zaman bu vektörü normalleştirirsiniz, ancak sin² + cos² ≡ 1zaten zaten normalize olduğundan! İlgilendiğiniz iki nesneyi birbirine bağlayan orijinal vektörü kullanabilir ve bunu normalleştirebilirsiniz. Hiçbir trig, ihtiyaç duyulanı çağırmaz.
leftaroundabout

XNA kullanımdan kaldırılmadı mı?
jcora

@yannbane, bu sorunun tartışmaya yardımcı olacağı bir şey eklemiyor. Ve hayır, XNA'nın statüsü herhangi bir kullanımdan kaldırılmış tanımına uymuyor.
Seth Battin

Yanıtlar:


36

Burada sahip olduğunuz klasik O (n²) algoritmasıdır. Sorununun kök nedeni, iş parçacığı ile ilgisi ve algoritması yüksek bir karmaşıklığı olduğu gerçeği ile ilgisi yoktur.

Daha önce "Büyük O" yazımına rastlamadıysanız, temel olarak n elemanlar üzerinde çalışmak için gereken işlem sayısı anlamına gelir (bu süper basitleştirilmiş açıklamadır). 100 elementiniz, loop'unuzun iç kısmını 10000 kez çalıştırıyor .

Oyun geliştirmede , küçük (ve tercihen sabitlenmiş veya sınırlandırılmış) miktarda bir veriye ve çok hızlı bir algoritmaya sahip olmadığınız sürece, genellikle O (n²) algoritmalarından kaçınmak istersiniz .

Eğer her varlık diğer varlıkları etkiliyorsa, zorunlu olarak bir O (n²) algoritması gerekir. Ancak görünen o ki, sadece birkaçı aslında birbiriyle etkileşime giriyor (nedeniyle if (distance < ...)) - bu nedenle " Spatial Partitioning " ( Uzamsal Bölünme) adı verilen bir şeyi kullanarak operasyon sayınızı önemli ölçüde azaltabilirsiniz .

Bu oldukça ayrıntılı bir konu ve oyuna özgü olduğundan, daha fazla ayrıntı için yeni bir soru sormanızı tavsiye ederim. Hadi devam edelim...


Kodunuzla ilgili en büyük performans sorunlarından biri oldukça basittir. Bu hilkat garibesi yavaş :

foreach (KeyValuePair<string, Entity> e in Entities)
{
    Entities[e.Key].Update();
}

Zaten sahip olduğunuz bir nesne için , her yinelemeyle (diğer döngülerinizde birden çok kez), sözlük taraması yapıyorsunuz !

Bunu yapabilirsin:

foreach (KeyValuePair<string, Entity> e in Entities)
{
    e.Value.Update();
}

Ya da bunu yapabilirsin: (Ben şahsen bunu daha çok seviyorum, her ikisi de aynı hızda olmalı)

foreach (Entity e in Entities.Values)
{
    e.Update();
}

Dize göre bir sözlük araması oldukça yavaştır. Doğrudan yineleme olacak önemli ölçüde daha hızlı.

Buna rağmen, gerçekte ne kadar sıklıkla isimleri aramaya ihtiyacınız var? Ne sıklıkta hepsini yinelemelisiniz? Sadece nadiren isim araması yaparsanız, varlıklarınızı bir öğede saklamayı düşünün List(bir Nameüye verin ).

Aslında sahip olduğunuz kod oldukça önemsiz. Profil oluşturmadım, ama bahse girerim ki yürütme zamanınızın çoğu tekrarlanan sözlük aramalarına gidiyor . Kodunuz sadece bu sorunu çözerek "yeterince hızlı" olabilir.

EDIT: Bir sonraki en büyük sorun muhtemelen çağırıyor Atan2ve sonra hemen Sinve ile Cos! Sadece vektörü doğrudan kullanın.


Son olarak, iş parçacığına ve kodunuzdaki ana konulara değinelim:

İlk ve en açıkçası: Her karede yeni bir iplik oluşturmayın! İplik nesneleri oldukça "ağır" dır. Bunun için en basit çözüm, ThreadPoolbunun yerine kullanmaktır .

Tabii ki, bu kadar basit değil. İki numaralı probleme geçelim: İki konudaki verilere aynı anda dokunmayın! (Uygun iş güvenliği altyapısı eklemeden.)

Burada temelde en korkunç şekilde belleği eziyorsunuz . Burada iplik güvenliği yoktur. gravity.UpdateBaşlatmakta olduğunuz çoklu " " iş parçacıklarından herhangi biri beklenmeyen zamanlarda başka bir iş parçacığında kullanılan verilerin üzerine yazıyor olabilir. Bu arada, ana iş parçacığınız da tüm bu veri yapılarına değineceğinden şüphe duymayacaksınız. Bu kodun çoğaltılması zor bellek erişim ihlalleri üretmesi beni şaşırtmaz.

Bu iplik gibi bir şey güvenli hale getirmek zordur ve genellikle çabaya değmeyecek şekilde yükü önemli performans ekleyebilir .


Ancak, nasılsa (nasıl olmasın), nasıl yapılacağına dair güzel bir şekilde görmek, hadi bunun hakkında konuşalım ...

Normalde, ipliğinizin temel olarak "ateşle ve unut" olduğu basit bir şey kullanarak başlamanızı öneririm. Ses çalmak, diske bir şeyler yazmak vb. Sonucu ana ipliğe geri beslemeniz gerektiğinde işler karışır.

Probleminize temel olarak üç yaklaşım var:

1) Dişler arasında kullandığınız tüm verileri kilitler . C # 'da bu lockaçıklama ile oldukça basit yapılır .

Genel olarak, new objectbazı veri kümelerini korumak için özel olarak kilitlemek için özel bir kilitleme yaratır (ve korur!) (Genel olarak yalnızca halka açık API'ler yazarken ortaya çıkan güvenlik nedenleriyle - aynı şekilde iyi bir stildir). Kilit nesnesini koruduğu verilere eriştiğin her yere kilitlemelisin !

Tabii ki, bir şey kullanımda olduğu için bir ipliğin "kilitli" olması ve başka bir ipliğin erişmeye çalışması durumunda - bu ikinci ipliğin ilk ipliğin bitmesini beklemek zorunda kalması gerekir. Dolayısıyla, paralel olarak yapılabilecek işleri dikkatlice seçmezseniz, temel olarak tek iş parçacıklı performans elde edersiniz (veya daha kötüsü).

Bu yüzden, sizin durumunuzda, oyununuzu, varlık koleksiyonunuza dokunmayacak şekilde başka bir kodun paralel çalışacağı şekilde tasarlayamazsanız, bunun anlamı yoktur.

2) Verileri iş parçacığına kopyalayın, işlemesine izin verin ve işlem tamamlandığında sonucu tekrar alın.

Tam olarak bunu nasıl uyguladığınız ne yaptığınıza bağlı olacaktır. Ancak açık bir şekilde bu, çoğu durumda yalnızca tek iş parçacıklı işlerden daha yavaş olacağı için potansiyel olarak pahalı bir kopya işlemi (veya iki) içerecektir.

Ve tabii ki, arka planda yapmak için yine de başka bir işiniz olmalı, aksi takdirde ana iş parçacığı sadece iş parçacığının bitmesini beklemek için oturmuş olacak, böylece verileri geri kopyalayabilir!

3) İplik güvenli veri yapıları kullanın.

Bunlar tek dişli dişlilerden daha yavaştır ve kullanımı basit kilitlemelere göre daha zordur. Dikkatli kullanmazsanız hala kilitleme problemlerini (performansı tek bir dişe düşürme) gösterebilirler.


Son olarak, bu çerçeve temelli bir simülasyon olduğundan, ana iş parçacığının diğer dişlilerin sonuçlarını vermesini beklemesi gerekir, böylece çerçeve oluşturulabilir ve simülasyon devam edebilir. Tam bir açıklama buraya koymak için gerçekten çok uzun, ama temelde Monitor.Waitve nasıl kullanılacağını öğrenmek isteyeceksiniz Monitor.Pulse. İşte sizi başlatan bir makale .


Bu uygulamalardan herhangi birine özel uygulama detayları (son bit hariç) veya kod vermediğimi biliyorum. Her şeyden önce, kapsayacak çok şey olurdu . Ve ikincisi, hiçbiri kendi kodunuz için tek başına uygulanamaz - tüm mimarinize iş parçacığı eklemek için bakmanız gerekir.

Threading sihirli bir şekilde orada sahip olduğunuz kodları daha hızlı yapmaz - aynı zamanda başka bir şey yapmanıza izin verir!


8
Yapabilseydim +10. Belki de son cümleyi bir giriş olarak en üste taşıyabilirsin, çünkü buradaki asıl konuyu özetliyor. Başka bir iş parçacığında kod çalıştırmak, aynı anda yapacak başka bir işiniz yoksa, oluşturma işlemini sihirli bir şekilde hızlandırmaz. Ve işleyici muhtemelen iş parçacığının bitmesini bekler, ancak yapmazsa (ve nasıl bilebilir?) Bazı varlık fiziğinin hala güncellenmesi için tutarsız bir oyun durumu çizecektir.
LearnCocos2D

İhtiyacım olan parçacığın ihtiyacım olmadığına ikna oldum, uzun ve bilgili bilgiler için teşekkür ederim! Performans iyileştirmelerine gelince, sizin (ve diğerlerinin) önerdiği değişiklikleri yaptım, ancak> 60 nesne ile çalışırken hala kötü performans gösteriyorum. N-Body simülasyon verimliliğine daha fazla odaklanan başka bir soru yapmamın en iyisi olacağını düşünüyorum. Yine de buna cevabımı alıyorsun. Teşekkürler!
Postacı

1
Bir şey değil, yardımcı oldun :) Yeni sorunuzu gönderdiğinizde, lütfen buraya bir bağlantı bırakın, böylece ben ve ardından gelen herkes görsün.
Andrew Russell

@Postman Genel olarak bu cevabın ne dediğine katılıyorum, ancak bunun diş açma avantajlarından yararlanmak için temel olarak MÜKEMMEL bir algoritma olduğu gerçeğini tamamen kaçırdığını düşünüyorum. Bunları GPU'da yapmaları için bir neden var ve bunun nedeni, eğer yazmayı ikinci bir adıma geçirirseniz önemsiz olarak paralel bir algoritma olması. Güvenli veri yapılarını kilitlemeye, kopyalamaya veya iş parçacığına gerek yoktur. Basit bir Parallel.ForEach ve onun için hiçbir sorun ile bitti.
Chewy Gumball

@ChewyGumball Çok geçerli bir nokta! Ve, Postacı algoritmasını iki aşamalı yapmak zorunda kalacak olsa da, muhtemelen iki aşamalı olması gerekir. Yine de, Parallelek yükü olmayan bir şeye işaret etmeye değiyor, bu yüzden kesinlikle profilden bir şey - özellikle bu tür küçük veri setleri ve (ne olması gerektiği) nispeten hızlı bir kod parçası için. Ve elbette, bu durumda algoritma karmaşıklığını azaltmak belki de daha iyidir - sadece ona paralellik atmak yerine.
Andrew Russell

22

Tamam ilk bakışta denemeniz gereken bazı şeyler var. İlk başta çarpışma çeklerinizi azaltmaya çalışmalısınız, bunu dörtlü gibi bir çeşit uzamsal yapı kullanarak yapabilirsiniz . Bu, ikinci foreach sayısını azaltmanıza izin verir, çünkü yalnızca birinciyi kapatan varlıkları sorgulayacaksınız.

Konu ile ilgili olarak: Her Güncelleme dönüşünde bir konu oluşturmaya çalışmayın. Bu yük, belki de işinizi hızlandırmaktan daha yavaşlatıyor. Bunun yerine, tek bir çarpışma ipliği oluşturmayı deneyin ve işi sizin için yapmasına izin verin. Somut kopyala-yapıştır-bu-kod yaklaşımım yok, fakat konu senkronizasyonu ve C # için arka plan çalışanı ile ilgili makaleler var.

Diğer bir nokta, foreach döngüsünde yapmanız gerekmediği içindir entityEngine.Entities[e.Key].Textureçünkü foreach başlığınızdaki dict'a zaten eriştiniz. Bunun yerine sadece yazabilirsiniz e.Texture. Bunun etkisini gerçekten bilmiyorum, sadece size bildirmek istedim;)

Son bir şey: Şu anda her varlığı iki kez kontrol ediyorsunuz, çünkü ilk VE ikinci foreach döngüsünde sorgulanıyor.

2 varlık A ve B örneği:

pick A in first foreach loop
   pick A in second foreach loop
      skip A because keys are the same
   pick B in second foreach loop
      collision stuff
pick B in first foreach loop
   pick A in second foreach loop
      collision stuff
   pick B in second foreach loop
      skip B because keys are the same

Bu olası bir yaklaşım olsa da, çarpışma kontrollerinizin yarısını atlayarak belki bir sırayla A ve B'yi kullanabilirsiniz.

Umarım bu size başlar =)

Not: Bunu duymak istemediğinizi söyleseniz bile: Çarpışma algılamayı aynı iş parçacığında tutmaya çalışın ve sadece yeterince hızlandırın. Threading iyi bir fikir gibi görünüyor ama bununla cehennem gibi senkronize etmek gerekir. Eğer çarpışma kontrolü sizin güncellemenizden daha yavaş ise (onu sallama sebebi), aksaklıklar ve hatalar alırsınız, çünkü çarpışmalar gemiler çoktan taşındıktan sonra tetiklenir ve tersi de geçerlidir. Seni cesaret kırmak istemiyorum, bu sadece kişisel bir tecrübeyle.

EDIT1: QuadTree öğreticiyle bağlantılar (Java): http://gamedev.tutsplus.com/tutorials/implementation/quick-tip-use-quadtrees-to-detect-likely-collisions-in-2d-space/


10
Yerçekimi simülasyonu için quad / octrees kullanma konusundaki güzel şey, uzaktaki parçacıkları görmezden gelmek yerine, toplam kütle ve bütün parçacıkların kütle merkezini ağacınızın her dalında saklayabilmeniz ve bunu ortalama yerçekimi etkisini hesaplamak için kullanabilmenizdir. bu daldaki tüm parçacıkların diğer uzak parçacıklarda. Bu olarak bilinir Barnes-Hut algoritması ve bunun artıları kullandığım şey .
Ilmari Karonen

10

Açıkçası, yapmanız gereken ilk şey daha iyi bir algoritmaya geçmek.

Simülasyonunuzu paralel hale getirmek, mümkün olan en iyi durumda bile, yalnızca sisteminizde mevcut olan her bir işlemci için çekirdek sayısı × CPU başına çekirdek sayısı × çekirdek sayısına eşit bir faktörle hızlandırabilir - yani modern bir PC için 4 - 16 arasında. (Kodunuzu GPU'ya taşımak , ekstra geliştirme karmaşıklığı maliyetine ve iş parçacığı başına temel hesaplama hızına bağlı olarak çok daha etkileyici paralelleştirme faktörleri sağlayabilir .) Örnek kodunuz gibi bir O (n²) algoritmasıyla, Şu anda sahip olduğunuz kadar 2 ila 4 kez kadar parçacık kullanın.

Tersine, daha verimli bir algoritmaya geçmek, simülasyonunuzu 100 ila 10000 (sayılar tamamen tahmin edilmiştir) ile kolayca hızlandırabilir. Uzamsal alt bölmeyi kullanan iyi n-vücut simülasyonu algoritmalarının zaman karmaşıklığı, kabaca "neredeyse doğrusal" olan O (n log n) olarak ölçeklenir, böylece işleyebileceğiniz parçacık sayısında neredeyse aynı artış faktörünü bekleyebilirsiniz. Ayrıca, bu hala sadece bir iplik kullanıyordu, bu yüzden bunun üzerinde paralellik için hala yer olacaktı .

Her neyse, diğer cevapların da belirttiği gibi, çok sayıda etkileşimli parçacıkları etkin biçimde simüle etmenin genel hilesi, onları bir dörtlü (2B) veya bir sekizli (3B) olarak organize etmektir . Özellikle, yerçekimi simülasyonu için, kullanmak istediğiniz temel algoritma , dörtlü / sekizli her bir hücrede bulunan tüm parçacıkların toplam kütlesini (ve kütle merkezini) sakladığınız Barnes-Hut simülasyon algoritmasıdır ve bunu, bu hücrede bulunan parçacıkların diğer uzak parçacıklardaki ortalama yerçekimi etkisini yaklaşık olarak belirlemek için kullanın.

Googling'in Barnes-Hut algoritması hakkında birçok açıklama ve öğretici yazılım bulabilirsiniz , ancak burada başlamanız için güzel ve basit bir açıklama sunarken , galaksi çarpışmalarının GPU simülasyonunda kullanılan gelişmiş bir uygulamanın bir açıklaması .


6

Konu ile ilgisi olmayan başka bir optimizasyon cevabı. Bunun için üzgünüm.

Her çiftin Uzaklık () değerini hesaplıyorsunuz. Bu, yavaş bir kare kök almayı içerir. Ayrıca, gerçek boyutları elde etmek için birkaç nesne araması da içerir.

Bunu yerine DistanceSquared () işlevini kullanarak optimize edebilirsiniz. Herhangi iki nesnenin etkileşime girebileceği maksimum mesafeyi önceden hesaplayın, kare çizin ve sonra bunu DistanceSquared () ile karşılaştırın. Eğer ve sadece kare kare uzaklığı maksimumda ise, karekökü alın ve gerçek nesne boyutlarıyla karşılaştırın.

EDIT : Bu optimizasyon çoğunlukla çarpışmalar için test yaptığınız zaman içindir, şimdi farkettim ki aslında ne yaptığınız değil (bir noktada mutlaka göreceksiniz). Yine de, eğer bütün parçacıklar aynı büyüklükte / kütleye sahipse durumunuz için hala geçerli olabilir.


Evet. Bu çözüm iyi olabilir (yalnızca ihmal edilebilir doğruluk kaybı), ancak nesnelerin kütlesi çok değiştiğinde başını belaya sokar. Bazı nesnelerin kütlesi çok büyükse, bazı nesnelerin kütlesi çok küçükse, makul olan maksimum mesafe daha yüksektir. Örneğin, yerçekiminin küçük bir toz parçacıkları üzerindeki etkisi, toprak için önemsizdir, ancak toz parçacıkları için (oldukça büyük bir mesafe için) önemsizdir. Ancak aslında aynı mesafedeki iki toz parçacığı birbirini önemli ölçüde etkilemez.
SDwarfs

Aslında bu çok iyi bir nokta. Bunu çarpışma testi olarak yanlış anladım, ama aslında tam tersini yapıyor: dokunmazlarsa parçacıklar birbirlerini etkiler.
Alistair Buxton,

3

Diş açma hakkında pek bir şey bilmiyorum, ama döngüleriniz zaman alıyor gibi görünüyor, belki de bundan değişiyor

i = 0; i < count; i++
  j = 0; j < count; j++

  object_i += force(object_j);

buna

i = 0; i < count-1; i++
  j = i+1; j < count; j++

  object_i += force(object_j);
  object_j += force(object_i);

yardımcı olabilir


1
bu neden yardımcı oldu?

1
Çünkü ilk iki döngü 10.000 yineleme yapar, ancak ikinci döngü yalnızca 4 950 yineleme yapar.
Buksy

1

Simüle edilmiş 10 nesneyle zaten böyle büyük problemleriniz varsa, kodu optimize etmeniz gerekir! İç içe döngünüz, yalnızca 10 x 10 yinelemeye neden olur; bunun 10 tekrarı atlanır (aynı nesne), iç döngünün 90 tekrarına neden olur. Sadece 2 FPS'ye ulaşırsanız, performansınız o kadar kötü olur ki saniyede sadece 180 ite iç döngü elde edersiniz.

Aşağıdakileri yapmanızı öneririm:

  1. PREPARATION / BENCHMARKING: Bu rutinin problem olduğunu kesinlikle bilmek için küçük bir kıyaslama yordamı yazın. Bu yürütür Update()1000 kez örneğin için Yerçekimi birden çok kez yöntemini ve 's süreyi ölçün. 100 nesneyle 30 FPS elde etmek istiyorsanız, 100 nesneyi simüle etmeli ve 30 yürütme süresini ölçmelisiniz. 1 saniyeden az olmalıdır. Makul optimizasyon yapmak için böyle bir ölçüt kullanmak gerekir. Aksi takdirde, tam tersini başaracaksınız ve kodun daha yavaş çalışmasını sağlayacaksınız çünkü daha hızlı olması gerektiğini düşünüyorsunuz ... Bu yüzden bunu yapmanızı gerçekten tavsiye ediyorum!

  2. OPTİMİZASYONLAR: O (N²) çaba problemi hakkında fazla bir şey yapamazken (yani: hesaplama süresi simüle edilmiş nesne sayısı N ile dört kat artar), kodun kendisini geliştirebilirsiniz.

    a) Kodunuzda birçok "ilişkisel dizi" (Sözlük) araması kullanıyorsunuz. Bunlar yavaş! Örneğin entityEngine.Entities[e.Key].Position. Sadece kullanamaz e.Value.Positionmısın? Bu tek bir arama tasarrufu sağlar. E ve e2 tarafından referans verilen nesnelerin özelliklerine erişmek için bunu tüm iç döngüde her yerde yaparsınız ... Bunu değiştirin! b) Döngünün içinde yeni bir Vektör oluşturun new Vector2( .... ). Tüm "yeni" çağrılar, bazı bellek ayırma işlemlerini içerir (ve daha sonra: devre dışı bırakma). Bunlar, Sözlüklerin aranmasından çok daha yavaştır. Bu Vektöre yalnızca geçici olarak ihtiyaç duyuyorsanız, yeni bir nesne oluşturmak yerine, değerlerini yeni değerlere yeniden başlatarak, bunu ANDA döngüsünün dışında ayırın. c) Çok fazla trigonometrik işlev kullanıyorsunuz (örneğin atan2vecos) döngü içinde. Doğruluk düzeyinizin gerçekten kesin olması gerekmiyorsa, bunun yerine bir arama tablosu kullanmayı deneyebilirsiniz. Bunu yapmak için, değerinizi tanımlanmış bir aralığa ölçeklendirin, bir tamsayı değerine getirin ve önceden hesaplanmış sonuçlar tablosuna bakın. Bununla ilgili yardıma ihtiyacınız olursa, sadece sorun. d) Sık kullanıyorsunuz .Texture.Width / 2. Bunu önceden hesaplayabilir ve sonucu .Texture.HalfWidthya da -e her zaman pozitif bir tamsayı değeri olarak kaydedebilirsiniz - bit kaydırma işlemini >> 1iki ile saptırmak için kullanabilirsiniz.

Bir seferde değişikliklerden yalnızca birini yapın ve çalışma zamanınızı nasıl etkilediğini görmek için ölçüt tarafından yapılan değişikliği ölçün! Belki bir şey iyidir, diğer fikir kötü iken (ben onları yukarıda önerdim bile!) ...

Bu optimizasyonların birden fazla iş parçacığı kullanarak daha iyi performans elde etmeye çalışmaktan daha iyi olacağını düşünüyorum! Dişleri koordine etmek için çok zorlanacaksınız, böylece diğer değerlerin üzerine yazılmayacaklar. Ayrıca, benzer hafıza bölgelerine erişirken de çatışacaklar. Bu iş için 4 CPU / Konu kullanırsanız, kare hızı için sadece 2 ila 3'lük bir hız bekleyebilirsiniz.


0

Nesne oluşturma çizgileri olmadan yeniden çalıştırabiliyor musunuz?

Vector2 Force = yeni Vector2 ();

Vector2 VecForce = yeni Vector2 ((float) Math.Cos (açı), (float) Math.Sin (angle));

Belki de her seferinde iki yeni nesne yaratmak yerine kuvvet değerini işletmeye yerleştirirseniz, performansı iyileştirmeye yardımcı olabilir.


4
Vector2XNA'da bir değer türüdür . GC ek yükü yoktur ve yapı ek yükü önemsizdir. Bu sorunun kaynağı değil.
Andrew Russell

@Andrew Russell: Emin değilim, ama "yeni Vector2" kullanıyorsanız, bu gerçekten hala geçerli mi? Vector2 (....) işlevini "new" kullanmadan kullanırsanız, bu muhtemelen farklı olacaktır.
SDwarfs

1
@StefanK. C # 'da bunu yapamazsınız. Yeni ihtiyacı var. C ++ 'ı mı düşünüyorsun?
MrKWatkins
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.