Perhitungan gravitasi 2D multithreading


24

Saya sedang membangun game eksplorasi ruang angkasa dan saya saat ini mulai mengerjakan gravitasi (In C # with XNA).

Gravitasi masih perlu diubah, tetapi sebelum saya bisa melakukan itu, saya perlu mengatasi beberapa masalah kinerja dengan perhitungan fisika saya.

Ini menggunakan 100 objek, biasanya menghasilkan 1000 dari mereka tanpa perhitungan fisika mendapatkan lebih dari 300 FPS (yang merupakan batas FPS saya), tetapi lebih dari 10 atau lebih objek membawa game (dan satu utas berjalan) untuk itu berlutut saat melakukan perhitungan fisika.

Saya memeriksa penggunaan utas saya dan utas pertama mematikan sendiri dari semua pekerjaan, jadi saya pikir saya hanya perlu melakukan perhitungan fisika pada utas lain. Namun ketika saya mencoba menjalankan metode Pembaruan kelas Gravity.cs di utas lain, bahkan jika metode Pembaruan Gravity tidak memiliki apa pun di dalamnya, permainan masih turun menjadi 2 FPS.

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;
        }

    }

Ya aku tahu. Itu adalah loop foreach bersarang, tapi saya tidak tahu bagaimana lagi melakukan perhitungan gravitasi, dan ini tampaknya berhasil, itu hanya begitu intensif sehingga perlu utas sendiri. (Bahkan jika seseorang tahu cara super efisien untuk melakukan perhitungan ini, saya masih ingin tahu bagaimana saya BISA melakukannya pada banyak utas sebagai gantinya)

EntityEngine.cs (mengelola instance dari Gravity.cs)

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 dibuat di Game1.cs, dan metode Pembaruan () disebut di dalam Game1.cs.

Saya membutuhkan perhitungan fisika saya di Gravity.cs untuk menjalankan setiap kali pembaruan game, di utas terpisah sehingga perhitungan tidak memperlambat permainan ke FPS yang sangat rendah (0-2).

Bagaimana cara saya membuat karya threading ini? (setiap saran untuk sistem Gravitasi Planet yang ditingkatkan dipersilahkan jika ada yang memilikinya)

Saya juga tidak mencari pelajaran mengapa saya tidak boleh menggunakan threading atau bahaya menggunakannya secara tidak benar, saya mencari jawaban langsung tentang bagaimana melakukannya. Saya sudah menghabiskan satu jam mencari pertanyaan ini dengan sedikit hasil yang saya mengerti atau sangat membantu. Saya tidak bermaksud kasar, tetapi selalu tampak sulit sebagai pemrograman noob untuk mendapatkan jawaban yang bermakna langsung, saya biasanya lebih suka mendapatkan jawaban yang begitu rumit sehingga saya bisa dengan mudah menyelesaikan masalah saya jika saya memahaminya, atau seseorang mengatakan mengapa saya tidak harus melakukan apa yang ingin saya lakukan dan tidak menawarkan alternatif (yang membantu).

Terima kasih atas bantuannya!

EDIT : Setelah membaca jawaban yang saya dapatkan, saya melihat bahwa kalian benar-benar peduli dan tidak hanya mencoba memuntahkan jawaban yang mungkin berhasil. Saya ingin membunuh dua burung dengan satu batu (meningkatkan kinerja dan mempelajari beberapa dasar-dasar multlebar), tetapi tampaknya sebagian besar masalahnya terletak pada perhitungan saya dan bahwa threading lebih merepotkan daripada layak untuk peningkatan kinerja. Terima kasih semua, saya akan membaca jawaban Anda lagi dan mencoba solusi Anda ketika saya selesai dengan sekolah, Terima kasih lagi!


Apa yang dilakukan [Sistem threading pembaruan Anda yang diuraikan di atas] sekarang (apakah berfungsi)? Tapi saya akan mulai ASAP dalam siklus permainan - misalnya sebelum entitas diperbarui.
ThorinII

2
Panggilan Trig di interior loop bersarang Anda mungkin merupakan hit terbesar. Jika Anda dapat menemukan cara untuk menghilangkannya, itu akan mengurangi banyak masalah kini O(n^2).
RBarryYoung

1
Memang panggilan trigonometri sama sekali tidak perlu : Anda pertama menghitung sudut dari vektor, kemudian menggunakannya untuk menghasilkan vektor lain yang menunjuk ke arah yang diberikan. Maka Anda menormalkan vektor itu, tetapi karena sin² + cos² ≡ 1itu sudah dinormalisasi pula! Anda bisa saja menggunakan vektor asli yang menghubungkan dua objek yang Anda minati, dan menormalkan yang ini. Tidak ada panggilan panggilan apa pun yang diperlukan.
leftaroundabout

Bukankah XNA sudah usang?
jcora

@yannbane pertanyaan itu tidak menambah apa pun yang bermanfaat untuk diskusi. Dan tidak, status XNA tidak cocok dengan definisi usang.
Seth Battin

Jawaban:


36

Apa yang Anda miliki di sini adalah algoritma O (n²) klasik . Penyebab utama masalah Anda tidak ada hubungannya dengan threading dan semuanya terkait dengan fakta bahwa algoritme Anda memiliki kompleksitas tinggi.

Jika Anda belum menemukan notasi "O Besar" sebelumnya, pada dasarnya berarti jumlah operasi yang diperlukan untuk bekerja pada elemen n (ini adalah penjelasan yang sangat disederhanakan). 100 elemen Anda mengeksekusi bagian dalam loop Anda sebanyak 10.000 kali .

Dalam pengembangan game, Anda umumnya ingin menghindari algoritma O (n²) , kecuali Anda memiliki sejumlah kecil data (dan lebih disukai diperbaiki atau dibatasi) dan algoritma yang sangat cepat.

Jika setiap entitas memengaruhi setiap entitas lain, Anda tentu membutuhkan algoritma O (n²). Tetapi sepertinya hanya beberapa entitas yang benar-benar berinteraksi (karena if (distance < ...)) - sehingga Anda dapat secara signifikan mengurangi jumlah operasi Anda dengan menggunakan sesuatu yang disebut " Partisi Spasial ".

Karena ini adalah topik yang cukup terperinci dan agak khusus untuk permainan, saya sarankan Anda mengajukan pertanyaan baru untuk lebih jelasnya. Mari kita lanjutkan ...


Salah satu masalah kinerja utama dengan kode Anda cukup sederhana. Ini sangat lambat :

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

Anda sedang melakukan pencarian kamus dengan string, setiap iterasi (beberapa kali di loop Anda yang lain), untuk objek yang sudah Anda miliki!

Anda bisa melakukan ini:

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

Atau Anda bisa melakukan ini: (Saya pribadi lebih suka ini, keduanya harus dengan kecepatan yang sama)

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

Pencarian kamus dengan string cukup lambat. Iterasi secara langsung akan jauh lebih cepat.

Meskipun, seberapa sering Anda benar - benar perlu mencari item berdasarkan nama? Dibandingkan dengan seberapa sering Anda harus mengulanginya semua? Jika Anda jarang melakukan pencarian nama, pertimbangkan untuk menyimpan entitas Anda di List(beri mereka Nameanggota).

Kode yang Anda miliki sebenarnya relatif sepele. Saya belum memprofilkannya, tapi saya yakin sebagian besar waktu eksekusi Anda akan ke pencarian kamus berulang . Kode Anda mungkin "cukup cepat" hanya dengan memperbaiki masalah ini.

EDIT: Masalah terbesar berikutnya mungkin memanggil Atan2dan kemudian segera mengubahnya kembali menjadi vektor dengan Sindan Cos! Cukup gunakan vektor secara langsung.


Terakhir, mari kita bahas threading, dan masalah utama dalam kode Anda:

Pertama dan yang paling jelas: Jangan membuat utas baru setiap frame! Objek utas cukup "berat". Solusi paling sederhana untuk ini adalah dengan menggunakan ThreadPoolsaja.

Tentu saja, itu tidak sesederhana itu. Mari kita beralih ke masalah nomor dua: Jangan menyentuh data pada dua utas sekaligus! (Tanpa menambahkan infrastruktur keselamatan-ulir yang sesuai.)

Anda pada dasarnya menginjak memori di sini dengan cara yang paling mengerikan . Tidak ada keamanan utas di sini. Salah satu dari beberapa gravity.Updateutas yang Anda mulai dapat menimpa data yang digunakan di utas lain pada waktu yang tidak terduga. Utas utama Anda, sementara itu, tidak diragukan lagi akan menyentuh semua struktur data ini juga. Saya tidak akan terkejut jika kode ini menghasilkan pelanggaran akses memori yang sulit direproduksi.

Membuat sesuatu seperti utas ini aman adalah sulit dan dapat menambah overhead kinerja yang signifikan sehingga seringkali tidak sepadan dengan usaha.


Tapi, melihat ketika Anda bertanya (tidak begitu) dengan baik tentang bagaimana melakukannya, mari kita bicara tentang itu ...

Biasanya saya akan merekomendasikan memulai dengan berlatih sesuatu yang sederhana, di mana utas Anda pada dasarnya "api dan lupakan". Memutar audio, menulis sesuatu ke disk, dll. Hal menjadi rumit ketika Anda harus memasukkan hasilnya kembali ke utas utama.

Pada dasarnya ada tiga pendekatan untuk masalah Anda:

1) Letakkan kunci di sekitar semua data yang Anda gunakan di utas. Dalam C # ini dibuat cukup sederhana dengan lockpernyataan itu.

Umumnya Anda membuat (dan menyimpan!) new objectKhusus untuk mengunci untuk melindungi beberapa set data (itu untuk alasan keamanan yang umumnya hanya muncul ketika menulis API publik - tetapi gaya yang baik semuanya sama). Anda kemudian harus mengunci objek kunci Anda di mana pun Anda mengakses data yang dilindungi!

Tentu saja, jika ada sesuatu yang "dikunci" oleh satu utas karena sedang digunakan, dan utas lainnya mencoba mengaksesnya - utas kedua kemudian akan dipaksa untuk menunggu sampai utas pertama selesai. Jadi, kecuali Anda dengan hati-hati memilih tugas yang dapat dilakukan secara paralel, pada dasarnya Anda akan mendapatkan kinerja single-threaded (atau lebih buruk).

Jadi dalam kasus Anda, tidak ada gunanya melakukan hal ini kecuali Anda dapat membuat game Anda sedemikian rupa sehingga beberapa kode lain berjalan secara paralel yang tidak akan menyentuh koleksi entitas Anda.

2) Salin data ke utas, biarkan proses, dan kemudian ambil hasilnya lagi setelah selesai.

Bagaimana tepatnya Anda menerapkan ini akan tergantung pada apa yang Anda lakukan. Tetapi jelas ini akan melibatkan operasi penyalinan yang berpotensi mahal (atau dua) yang dalam banyak kasus akan lebih lambat daripada hanya melakukan hal-hal yang di-threaded tunggal.

Dan, tentu saja, Anda masih harus memiliki beberapa pekerjaan lain untuk dilakukan di latar belakang, jika tidak utas utama Anda hanya akan duduk menunggu thread lain selesai sehingga dapat menyalin data kembali!

3) Gunakan struktur data thread-safe.

Ini sedikit lebih lambat dari rekan single-threaded mereka dan seringkali lebih sulit untuk digunakan daripada penguncian sederhana. Mereka masih dapat menunjukkan masalah penguncian (mengurangi kinerja hingga satu utas) kecuali Anda menggunakannya dengan hati-hati.


Terakhir, karena ini adalah simulasi berbasis bingkai, Anda harus meminta utas utama menunggu utas lainnya memberikan hasil, sehingga bingkai dapat dirender dan simulasi dapat dilanjutkan. Penjelasan lengkap benar-benar terlalu lama untuk dimasukkan di sini, tetapi pada dasarnya Anda ingin mempelajari cara menggunakan Monitor.Waitdan Monitor.Pulse. Ini adalah artikel untuk memulai Anda .


Saya tahu saya belum memberikan detail implementasi spesifik (kecuali bit terakhir) atau kode untuk salah satu pendekatan ini. Pertama-tama, akan ada banyak yang harus dibahas. Dan, kedua, tidak ada yang dapat diterapkan pada kode Anda sendiri - Anda perlu mendekati seluruh arsitektur Anda dengan maksud menambahkan threading.

Threading tidak akan secara ajaib kode yang Anda miliki ada lebih cepat - itu hanya memungkinkan Anda melakukan sesuatu yang lain pada saat yang sama!


8
+10 jika aku bisa. Mungkin Anda dapat memindahkan kalimat terakhir ke atas sebagai pengantar, karena merangkum masalah inti di sini. Menjalankan kode di utas lain tidak secara ajaib mempercepat rendering jika Anda tidak memiliki hal lain untuk dilakukan pada saat yang sama. Dan penyaji mungkin menunggu utasnya selesai tetapi jika tidak (dan bagaimana bisa tahu?) Itu akan menggambar keadaan permainan yang tidak konsisten dengan beberapa fisika entitas yang masih akan diperbarui.
LearnCocos2D

Saya sepenuhnya yakin bahwa threading bukanlah yang saya butuhkan, terima kasih atas informasi yang panjang dan luas! Sedangkan untuk peningkatan kinerja, saya membuat perubahan yang Anda (dan yang lainnya) sarankan, tetapi saya masih mendapatkan kinerja yang buruk ketika berhadapan dengan> 60 objek. Saya pikir akan lebih baik bagi saya untuk membuat pertanyaan lain yang lebih fokus pada efisiensi simulasi N-Body. Anda mendapatkan jawaban saya untuk ini. Terima kasih!
Tukang

1
Terima kasih, senang ini membantu :) Ketika Anda memposting pertanyaan baru Anda, silakan letakkan tautan di sini sehingga saya, dan siapa pun yang mengikuti, akan melihatnya.
Andrew Russell

@ Postman Sementara saya setuju dengan apa yang dikatakan jawaban ini secara umum, saya pikir itu benar-benar melewatkan fakta bahwa ini pada dasarnya adalah algoritma PERFECT untuk mengambil keuntungan dari threading. Ada alasan mereka melakukan hal-hal ini pada GPU dan itu karena itu adalah algoritma paralel sepele jika Anda memindahkan tulisan ke langkah kedua. Tidak perlu untuk mengunci atau menyalin atau utas struktur data yang aman. Paralel sederhana. FOREach dan dilakukan tanpa masalah.
Chewy Gumball

@ ChewyGumball Poin yang sangat valid! Dan, sementara Postman harus membuat algoritma-nya dua fase, itu bisa dibilang harus dua fase. Akan tetapi, patut untuk ditunjukkan bahwa Parallelitu bukan tanpa overhead, jadi itu pasti sesuatu yang harus diprofilkan - terutama untuk set data kecil dan (apa yang seharusnya) sepotong kode yang relatif cepat. Dan, tentu saja, masih bisa dibilang lebih baik untuk mengurangi kompleksitas algoritme dalam kasus ini - daripada sekadar melemparkan paralelisme padanya.
Andrew Russell

22

Ok pada pandangan pertama ada beberapa hal yang harus Anda coba. Pada awalnya Anda harus mencoba mengurangi cek tabrakan, Anda dapat melakukan ini dengan menggunakan semacam struktur spasial seperti quadtree . Ini akan memungkinkan Anda untuk mengurangi jumlah foreach kedua, karena Anda hanya akan meminta entitas menutup yang pertama.

Mengenai threading Anda: Cobalah untuk tidak membuat utas setiap Pembaruan. Overhead ini mungkin memperlambat Anda lebih dari itu mempercepat. Alih-alih mencoba membuat utas tubrukan tunggal dan biarkan ia bekerja untuk Anda. Saya tidak punya pendekatan copy-paste-kode- konkret ini , tetapi ada artikel tentang sinkronisasi dan pekerja latar belakang untuk C #.

Poin lain adalah bahwa di loop foreach Anda tidak perlu melakukan entityEngine.Entities[e.Key].Texturekarena Anda sudah mengakses dict di header foreach Anda. Sebaliknya Anda bisa menulis e.Texture. Saya tidak benar-benar tahu tentang dampak ini, hanya ingin memberi tahu Anda;)

Satu hal terakhir: Pada saat ini Anda memeriksa dua kali setiap entitas, karena akan dipertanyakan di loop pertama DAN kedua.

Contoh dengan 2 entitas A dan B:

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

Meskipun ini merupakan pendekatan yang mungkin, mungkin Anda dapat menangani A dan B dalam satu giliran, melewatkan setengah dari pemeriksaan tabrakan Anda

Semoga ini bisa Anda mulai =)

PS: Sekalipun Anda mengatakan tidak ingin mendengarnya: Usahakan agar deteksi tabrakan tetap berada di utas yang sama dan percepat saja. Threading sepertinya ide yang bagus tetapi dengan ini muncul kebutuhan untuk melakukan sinkronisasi seperti neraka. Jika Anda memeriksa tabrakan lebih lambat dari pembaruan Anda (alasan untuk memasukkannya) Anda AKAN mendapatkan gangguan dan kesalahan, karena tabrakan akan memicu setelah kapal sudah bergerak dan sebaliknya. Saya tidak ingin mengecewakan Anda, ini hanya pengalaman pribadi.

EDIT1: Tautan dengan tutorial QuadTree (Java): http://gamedev.tutsplus.com/tutorials/implementation/quick-tip-use-quadtrees-to-detect- kemungkinan-collisions-in-2d-space/


10
Hal yang menyenangkan tentang menggunakan quad / octrees untuk simulasi gravitasi adalah bahwa, alih-alih mengabaikan partikel yang jauh, Anda dapat menyimpan massa total dan pusat massa semua partikel di setiap cabang pohon Anda dan menggunakannya untuk menghitung efek gravitasi rata-rata dari semua partikel di cabang ini pada partikel lain yang jauh. Ini dikenal sebagai algoritma Barnes-Hut , dan itulah yang digunakan oleh para profesional .
Ilmari Karonen

10

Jujur saja, hal pertama yang harus Anda lakukan adalah beralih ke algoritma yang lebih baik.

Menyejajarkan simulasi Anda dapat, bahkan dalam kasus terbaik, mempercepatnya hanya dengan faktor yang sama dengan jumlah CPU × core per CPU × threads per core yang tersedia di sistem Anda - yaitu sekitar 4 hingga 16 untuk PC modern. (Memindahkan kode Anda ke GPU dapat menghasilkan faktor paralelisasi yang jauh lebih mengesankan, dengan biaya kompleksitas pengembangan tambahan dan kecepatan perhitungan garis dasar per-thread yang lebih rendah.) Dengan algoritma O (n²), seperti kode contoh Anda, ini akan memungkinkan Anda gunakan partikel 2 hingga 4 kali lebih banyak dari yang Anda miliki saat ini.

Sebaliknya, beralih ke algoritme yang lebih efisien dapat dengan mudah mempercepat simulasi Anda dengan, katakanlah, faktor 100 hingga 10.000 (angka-angka murni perkiraan waktunya). Kompleksitas waktu dari algoritma simulasi n-tubuh yang baik menggunakan skala subdivisi spasial kira-kira seperti O (n log n), yang "hampir linier", sehingga Anda dapat mengharapkan faktor peningkatan yang hampir sama dalam jumlah partikel yang dapat Anda tangani. Selain itu, itu masih akan menggunakan hanya satu utas, jadi masih ada ruang untuk paralelisasi di atas itu .

Bagaimanapun, seperti jawaban lain telah dicatat, trik umum untuk mensimulasikan secara efisien sejumlah besar partikel yang berinteraksi adalah mengaturnya dalam quadtree (dalam 2D) atau octree (dalam 3D). Secara khusus, untuk mensimulasikan gravitasi, algoritma dasar yang ingin Anda gunakan adalah algoritma simulasi Barnes-Hut , di mana Anda menyimpan total massa (dan pusat massa) dari semua partikel yang terkandung dalam setiap sel quad / octree dan gunakan itu untuk memperkirakan efek gravitasi rata-rata dari partikel dalam sel itu pada partikel lain yang jauh.

Anda dapat menemukan banyak deskripsi dan tutorial tentang algoritma Barnes-Hut oleh Googling untuk itu, tetapi ini yang bagus dan sederhana untuk Anda mulai , sementara di sini adalah deskripsi implementasi lanjutan yang digunakan untuk simulasi GPU tabrakan galaksi.


6

Jawaban optimasi lain yang tidak ada hubungannya dengan utas. Maaf soal itu.

Anda menghitung Jarak () dari setiap pasangan. Ini melibatkan mengambil akar kuadrat, yang lambat. Ini juga melibatkan beberapa pencarian objek untuk mendapatkan ukuran sebenarnya.

Anda dapat mengoptimalkan ini menggunakan fungsi DistanceSquared () sebagai gantinya. Hitung ulang jarak maksimum di mana dua objek dapat berinteraksi, kuadratkan, dan kemudian bandingkan dengan DistanceSquared (). Jika dan hanya jika jarak kuadrat dalam maksimum, maka ambil akar kuadrat dan bandingkan dengan ukuran objek nyata.

EDIT : Optimalisasi ini sebagian besar untuk ketika Anda menguji tabrakan, yang sekarang saya perhatikan sebenarnya bukan apa yang Anda lakukan (meskipun Anda pasti akan pada titik tertentu). Mungkin masih berlaku untuk situasi Anda, jika semua partikel memiliki ukuran / massa yang sama


Ya. Solusi ini mungkin baik-baik saja (hanya kehilangan keakuratan yang dapat diabaikan), tetapi mendapat masalah ketika massa benda sangat berbeda. Jika massa beberapa benda sangat besar sedangkan massa beberapa benda sangat kecil, jarak maksimum yang masuk akal lebih tinggi. Misalnya efek gravitasi bumi pada partikel debu kecil dapat diabaikan untuk bumi, tetapi tidak untuk partikel debu (untuk jarak yang cukup besar). Namun pada kenyataannya dua partikel debu pada jarak yang sama tidak saling mempengaruhi secara signifikan.
SDwarfs

Sebenarnya itu poin yang sangat bagus. Saya salah membaca ini sebagai uji tabrakan, tetapi sebenarnya melakukan yang sebaliknya: partikel saling mempengaruhi jika mereka tidak menyentuh.
Alistair Buxton

3

Saya tidak tahu banyak tentang threading, tetapi sepertinya loop Anda memakan waktu, jadi mungkin berubah dari ini

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

  object_i += force(object_j);

untuk ini

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

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

bisa membantu


1
mengapa itu membantu?

1
Karena dua loop pertama menghasilkan 10.000 iterasi, tetapi loop kedua hanya menghasilkan 4 950 iterasi.
Buksy

1

Jika Anda sudah memiliki masalah besar dengan 10 objek simulasi, Anda harus mengoptimalkan kode! Loop bersarang Anda akan menyebabkan hanya 10 * 10 iterasi yang 10 iterasi dilewati (objek yang sama), menghasilkan 90 iterasi loop dalam. Jika Anda hanya mencapai 2 FPS, ini berarti bahwa kinerja Anda sangat buruk, bahwa Anda hanya mencapai 180 iterasi loop dalam per detik.

Saya sarankan Anda untuk melakukan hal berikut:

  1. PERSIAPAN / PEMBandingan: Untuk mengetahui dengan pasti bahwa rutin ini adalah masalahnya, tulislah sebuah patokan kecil rutin. Ini akan menjalankan Update()metode Gravity beberapa kali untuk mis 1000 kali dan mengukur waktunya. Jika Anda ingin mencapai 30 FPS dengan 100 objek, Anda harus mensimulasikan 100 objek dan mengukur waktu selama 30 eksekusi. Itu harus kurang dari 1 detik. Diperlukan tolok ukur seperti itu untuk melakukan optimasi yang masuk akal. Lain Anda mungkin akan mencapai yang sebaliknya dan membuat kode berjalan lebih lambat karena Anda hanya berpikir itu harus lebih cepat ... Jadi saya benar-benar mendorong Anda untuk melakukan ini!

  2. OPTIMASI: Meskipun Anda tidak dapat berbuat banyak tentang masalah upaya O (N²) (artinya: waktu perhitungan bertambah secara kuadrat dengan jumlah objek yang disimulasikan N), Anda dapat meningkatkan kode itu sendiri.

    a) Anda menggunakan banyak pencarian "array asosiatif" (Kamus) dalam kode Anda. Ini lambat! Sebagai contoh entityEngine.Entities[e.Key].Position. Tidak bisakah kamu menggunakan saja e.Value.Position? Ini menghemat satu pencarian. Anda melakukan ini di mana-mana di seluruh loop batin untuk mengakses properti objek yang dirujuk oleh e dan e2 ... Ubah ini! b) Anda membuat Vektor baru di dalam loop new Vector2( .... ). Semua panggilan "baru" melibatkan beberapa alokasi memori (dan yang lebih baru: deallocation). Ini bahkan jauh lebih lambat daripada pencarian Kamus. Jika Anda hanya membutuhkan Vector ini untuk sementara waktu, maka alokasikan saja di luar loop AND -useuse-it dengan menginisialisasi ulang nilainya ke nilai-nilai baru alih-alih membuat objek baru. c) Anda menggunakan banyak fungsi trigonometri (mis. atan2dancos) dalam loop. Jika akurasi Anda tidak perlu benar-benar tepat, Anda dapat mencoba menggunakan tabel pencarian saja. Untuk melakukan ini, Anda menskalakan nilai Anda ke rentang yang ditentukan, bulatkan ke nilai integer dan mencarinya di tabel hasil yang dihitung sebelumnya. Jika Anda perlu bantuan dengan itu, tanyakan saja. d) Anda sering menggunakan .Texture.Width / 2. Anda dapat menghitung sebelumnya dan menyimpan hasilnya sebagai .Texture.HalfWidthatau -jika ini selalu merupakan nilai integer yang positif - Anda dapat menggunakan sedikit operasi shift >> 1untuk membagi dua.

Lakukan hanya satu perubahan pada satu waktu dan ukur perubahan dengan patokan untuk melihat bagaimana itu mempengaruhi runtime Anda! Mungkin satu hal baik sementara ide lainnya buruk (bahkan saya mengusulkan mereka di atas!) ...

Saya pikir optimasi ini akan jauh lebih baik daripada mencoba untuk mencapai kinerja yang lebih baik dengan menggunakan banyak utas! Anda akan mengalami banyak kesulitan untuk mengoordinasikan utas, sehingga tidak akan menimpa nilai yang lain. Mereka juga akan konflik ketika mengakses wilayah memori yang sama juga. Jika Anda menggunakan 4 CPU / Thread untuk pekerjaan ini, Anda hanya bisa mengharapkan kecepatan 2 hingga 3 untuk frame rate.


0

Apakah Anda dapat mengerjakannya ulang tanpa garis-garis pembuatan objek?

Vector2 Force = new Vector2 ();

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

jika mungkin Anda bisa menempatkan nilai gaya ke entitas alih-alih membuat dua objek baru setiap kali, mungkin membantu untuk meningkatkan kinerja.


4
Vector2di XNA adalah tipe nilai . Tidak memiliki overhead GC dan overhead konstruksi dapat diabaikan. Ini bukan sumber masalahnya.
Andrew Russell

@ Andrew Russell: Saya tidak begitu yakin, tetapi apakah itu benar-benar masih terjadi jika Anda menggunakan "Vector2 baru"? Jika Anda menggunakan Vector2 (....) tanpa "baru", ini mungkin akan berbeda.
SDwarfs

1
@StefanK. Di C # Anda tidak bisa melakukan itu. Membutuhkan yang baru. Apakah Anda berpikir tentang C ++?
MrKWatkins
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.