Saya suka berpikir tentang kinerja dalam hal " batas ". Ini adalah cara praktis untuk membuat konsep sistem yang cukup rumit dan saling berhubungan. Ketika Anda memiliki masalah kinerja, Anda mengajukan pertanyaan: "Apa batasan yang saya pukul?" (Atau: "Apakah saya CPU / GPU terikat?")
Anda dapat memecahnya menjadi beberapa tingkatan. Pada level tertinggi Anda memiliki CPU dan GPU. Anda mungkin terikat dengan CPU (GPU duduk diam menunggu CPU), atau terikat GPU (CPU sedang menunggu di GPU). Berikut adalah posting blog yang bagus tentang topik tersebut.
Anda dapat memecahnya lebih lanjut. Di sisi CPU , Anda mungkin menggunakan semua siklus pada data yang sudah ada di cache CPU. Atau Anda mungkin terbatas memori , membiarkan CPU menunggu data masuk dari memori utama ( jadi optimalkan tata letak data Anda ). Anda masih bisa memecahnya lebih lanjut.
(Sementara saya melakukan tinjauan luas kinerja mengenai XNA, saya akan menunjukkan bahwa alokasi tipe referensi ( class
tidak struct
), walaupun biasanya murah, dapat memicu pengumpul sampah, yang akan membakar banyak siklus - terutama pada Xbox 360 . Lihat di sini untuk detail).
Di sisi GPU , saya akan mulai dengan mengarahkan Anda ke posting blog yang luar biasa ini yang memiliki banyak detail. Jika Anda ingin tingkat detail yang gila pada saluran pipa, baca seri posting blog ini . ( Ini yang lebih sederhana ).
Sederhananya, beberapa yang besar adalah: " batas pengisian " (berapa banyak piksel yang dapat Anda tulis ke pembuat backbuffer - sering berapa banyak overdraw yang Anda miliki), " batas shader " (seberapa rumitnya shader Anda dapat dan berapa banyak data yang Anda dapat mendorong melalui mereka), " tekstur-fetch / batas tekstur-bandwidth " (berapa banyak data tekstur yang dapat Anda akses).
Dan, sekarang, kita sampai pada yang besar - yang sebenarnya Anda tanyakan - di mana CPU dan GPU harus berinteraksi (melalui berbagai API dan driver). Secara longgar ada " batas batch " dan " bandwidth ". (Perhatikan bahwa bagian satu dari seri yang saya sebutkan sebelumnya masuk ke ekstensif rincian.)
Tapi, pada dasarnya, batch ( seperti yang sudah Anda ketahui ) terjadi setiap kali Anda memanggil salah satu GraphicsDevice.Draw*
fungsi (atau bagian dari XNA, seperti SpriteBatch
, melakukan ini untuk Anda). Seperti yang sudah pasti Anda baca, Anda mendapatkan beberapa ribu * dari ini per frame. Ini adalah batas CPU - sehingga bersaing dengan penggunaan CPU Anda yang lain. Ini pada dasarnya driver mengemas segala sesuatu tentang apa yang Anda perintahkan untuk menggambar, dan mengirimkannya ke GPU.
Dan kemudian ada bandwidth ke GPU. Ini adalah berapa banyak data mentah yang dapat Anda transfer ke sana. Ini mencakup semua informasi status yang sesuai dengan batch - semuanya mulai dari pengaturan status rendering dan konstanta shader / parameter (yang mencakup hal-hal seperti matriks dunia / tampilan / proyek), hingga simpul saat menggunakan DrawUser*
fungsi. Ini juga termasuk panggilan ke SetData
dan GetData
pada tekstur, buffer vertex, dll.
Pada titik ini saya harus mengatakan bahwa apa pun yang dapat Anda panggil SetData
(tekstur, simpul dan buffer indeks, dll), serta Effect
s - tetap ada dalam memori GPU. Ini tidak terus-menerus dikirim kembali ke GPU. Perintah draw yang mereferensikan data hanya dikirim dengan pointer ke data tersebut.
(Juga: Anda hanya bisa mengirim perintah menggambar dari utas utama, tetapi Anda bisa SetData
pada utas apa pun.)
XNA mempersulit hal-hal yang agak dengan kelas keadaan render ( BlendState
, DepthStencilState
, dll). Data status ini dikirim per panggilan undian (dalam setiap batch). Saya tidak 100% yakin, tetapi saya mendapat kesan bahwa itu dikirim dengan malas (hanya mengirimkan status yang berubah). Either way, perubahan negara murah ke titik gratis, relatif terhadap biaya satu batch.
Akhirnya, hal terakhir yang disebutkan adalah saluran internal GPU . Anda tidak ingin memaksanya untuk menyiram dengan menulis ke data yang masih perlu dibaca, atau membaca data yang masih perlu ditulis. Pipeline flush berarti menunggu operasi untuk selesai, sehingga semuanya dalam keadaan konsisten ketika data diakses.
Dua kasus khusus yang harus diwaspadai adalah: Menyerukan GetData
sesuatu yang dinamis - khususnya pada RenderTarget2D
GPU yang dapat ditulisi. Ini sangat buruk untuk kinerja - jangan lakukan itu.
Kasus lainnya adalah memanggil SetData
buffer vertex / indeks. Jika Anda perlu sering melakukan ini, gunakan DynamicVertexBuffer
(juga DynamicIndexBuffer
). Ini memungkinkan GPU untuk mengetahui bahwa mereka akan sering berubah, dan untuk melakukan sihir penyangga secara internal untuk menghindari flush pipeline.
(Perhatikan juga bahwa buffer dinamis lebih cepat daripada DrawUser*
metode - tetapi mereka harus dialokasikan sebelumnya pada ukuran maksimum yang diperlukan.)
... Dan itu semua yang saya tahu tentang kinerja XNA :)