Optimasi Umum
Aset memuat metrik

Optimasi khusus

Sementara bagian sebelumnya yang dijelaskan optimasi berlaku untuk semua proyek, optimasi detail bagian ini yang harus not diterapkan sebelum mengumpulkan data profil. Ini mungkin karena optimasi intensif tenaga kerja untuk mengimplementasikan, dapat mempromikan kebersihan kode atau pemeliharaan dalam mendukung kinerja, atau mungkin menyelesaikan masalah yang hanya muncul pada perbesaran skala tertentu.

Multidimensional vs. array bergerigi

Seperti yang dijelaskan dalam StackOverflow artikel ini, umumnya lebih efisien untuk menjauhkan array bergerigi daripada array multidimensi, sebagai array multidimensi memerlukan panggilan fungsi.

NOTES:

  • Ini adalah array dari array, dan dinyatakan sebagai type[x][y] bukan type[x,y].)

  • Ini dapat ditemukan dengan memeriksa IL yang dihasilkan dengan mengakses array multidimensi, menggunakan ILSpy atau alat serupa.)

Ketika diprofilkan dalam Unity 5.3, 100 iterasi parsial penuh atas array 100x100x100x100 tiga dimensi menghasilkan waktu berikut, yang rata-rata lebih dari 10 berjalan dari tes:

Jenis Array Total waktu (100 iterasi)
Satu Dimensi Login 660 ms
Jagged Arrays 730 ms
Array Multidimen 3470 ms

Biaya panggilan fungsi tambahan dapat dilihat dalam keparitas antara biaya akses array multidimensi vs. array satu dimensi, dan biaya iterating atas struktur memori non-tunjuk dapat dilihat dalam perbedaan antara mengakses array bergerigi vs. array satu dimensi.

Seperti yang ditunjukkan di atas, biaya fungsi tambahan memanggil sangat melebihi biaya yang dikenakan dengan menggunakan struktur memori yang tidak utuh.

Untuk operasi yang sangat sensitif, disarankan untuk menggunakan array satu dimensi. Untuk semua kasus lain di mana array dengan beberapa dimensi diperlukan, gunakan array bergerigi. array multidimensi tidak boleh digunakan.

Sistem Partikel

Ketika memadukan sistem partikel, menyadari bahwa mereka mengkonsumsi setidaknya 3500 byte memori. Konsumsi memori meningkat berdasarkan jumlah modul yang diaktifkan pada Particle SystemKomponen yang mensimulasikan entitas fluida seperti cairan, awan dan nyala dengan menghasilkan dan memikat sejumlah besar gambar 2D kecil di tempat kejadian. More info
Lihat di Glossary
. Memori ini adalah not yang dirilis ketika Sistem Partikel dinonaktifkan; Ini hanya dirilis ketika mereka hancur.

Sebagai Unity 5.3, sebagian besar pengaturan Sistem Partikel sekarang dapat dimanipulasi pada runtime. Untuk proyek yang harus memadukan sejumlah besar efek partikel yang berbeda, mungkin lebih efisien untuk mengekstrak parameter konfigurasi Sistem Partikel ke kelas atau struktur data-carrier.

Ketika efek partikel diperlukan, kolam efek partikel "generic" kemudian dapat memasok objek efek partikel requisite. Data konfigurasi kemudian dapat diterapkan pada objek untuk mencapai efek grafis yang diinginkan.

Ini secara substansial lebih hemat memori daripada mencoba untuk memadukan semua kemungkinan varian & konfigurasi Sistem Partikel yang digunakan dalam SceneAdegan berisi lingkungan dan menu permainan Anda. Pikirkan setiap file Adegan unik sebagai tingkat yang unik. Di setiap Adegan, Anda menempatkan lingkungan, hambatan, dan dekorasi, pada dasarnya merancang dan membangun permainan Anda dalam potongan-potongan. More info
Lihat di Glossary
tertentu, tetapi memerlukan upaya rekayasa yang substansial untuk mencapai.

Manajer pembaruan

Secara internal, daftar trek Unity objek yang tertarik dengan panggilannya, seperti Update, FixedUpdate dan LateUpdate. Ini dipelihara sebagai daftar yang tidak mengganggu untuk memastikan bahwa pembaruan daftar terjadi pada waktu yang konstan. MonoBehaviours ditambahkan ke / dihapus dari daftar ini ketika mereka Diaktifkan atau Diaktifkan, masing-masing.

Meskipun nyaman untuk menambahkan callback yang tepat ke MonoBehaviours yang membutuhkannya, ini menjadi semakin tidak efisien karena jumlah callback tumbuh. Ada overhead kecil namun signifikan untuk memanggil callback kode yang dikelola dari kode asli. Hasil ini baik dalam waktu bingkai yang terdegradasi ketika mengabdikan sejumlah besar metode per-frame, dan dalam waktu sekiasi yang terdegradasi ketika menginisiasi PrefabsJenis aset yang memungkinkan Anda untuk menyimpan GameObject lengkap dengan komponen dan properti. Prefab bertindak sebagai template dari mana Anda dapat membuat instance objek baru di tempat kejadian. More info
Lihat di Glossary
yang mengandung sejumlah besar MonoBehaviours (NOTE: Biaya seketika disebabkan oleh overhead kinerja memprovokasi Awake dan callback OnEnable pada setiap Komponen dalam prefab.).

Ketika jumlah MonoBehaviours dengan callback per-frame tumbuh menjadi ratusan atau ribuan, itu menguntungkan untuk menghilangkan panggilan ini dan bukan memiliki MonoBehaviours (atau bahkan standar C # objek) melampirkan ke manajer global tunggal. Manajer global singleton kemudian dapat mendistribusikan Update, LateUpdate dan callback lainnya untuk objek yang tertarik. Ini memiliki manfaat tambahan dari memungkinkan kode untuk secara cerdas berhenti berlangganan dari callback ketika mereka tidak akan ada-op, sehingga menyusut jumlah fungsi geser yang harus disebut setiap bingkai.

Penghematan terbesar biasanya direalisasikan dengan menghilangkan callback yang jarang dieksekusi. Pertimbangkan kode pseudo-code berikut:

void Update() {
    if(!someVeryRareCondition) { return; }
// … some operation …
}

Jika ada sejumlah besar MonoBehaviours dengan Update callback mirip dengan di atas, maka jumlah yang signifikan dari waktu yang dikonsumsi menjalankan callback Update dihabiskan beralih antara domain kode asli dan dikelola untuk eksekusi MonoBehaviour yang kemudian keluar segera. Jika kelas ini bukan berlangganan Manajer Pembaruan global hanya sementara someVeryRareCondition benar, dan berhenti berlangganan setelah itu, waktu akan disimpan kedua switching domain kode dan pada evaluasi kondisi langka.

Menggunakan C# delegasi dalam manajer pembaruan

Ini menggoda untuk menggunakan polos C# delegasi untuk menerapkan callback ini. Namun, implementasi delegasi C# dioptimalkan untuk tarif rendah berlangganan dan berhenti berlangganan, dan untuk jumlah callback rendah. A C # delegate melakukan daftar callback penuh dalam setiap kali callback ditambahkan atau dihapus. Daftar besar callback, atau sejumlah besar callbacks subscribing/unsubscribing selama satu bingkai hasil dalam lonjakan kinerja dalam metode Delegate.Combine internal.

Untuk kasus di mana adds/removes terjadi pada frekuensi tinggi, pertimbangkan menggunakan struktur data yang dirancang untuk sisipan cepat / bergerak bukan delegasi.

Memuat kontrol benang

Para pengembang bersatu untuk mengontrol prioritas benang latar belakang yang digunakan untuk memuat data. Hal ini sangat penting ketika mencoba untuk streaming AssetBundles ke disk di latar belakang.

Prioritas untuk benang utama dan benang grafis adalah kedua ThreadPriority.Normal - benang apa pun dengan prioritas yang lebih tinggi preempt benang utama / grafik dan menyebabkan kenaikan bingkai, sedangkan benang dengan prioritas yang lebih rendah tidak. Jika benang memiliki prioritas yang setara dengan benang utama, upaya CPU untuk memberikan waktu yang sama dengan benang, yang umumnya menghasilkan stuttering berbingkai jika beberapa benang latar belakang melakukan operasi berat, seperti dekompresi AssetBundle.

Saat ini, prioritas ini dapat dikontrol dalam tiga tempat.

Pertama, prioritas default untuk panggilan pemuatan Aset, seperti Resources.LoadAsync dan AssetBundle.LoadAssetAsync, diambil dari pengaturan Aplikasi.backgroundLoadingPrioritas. Seperti yang didokumentasikan, panggilan ini juga membatasi jumlah waktu bahwa benang utama menghabiskan aset mengintegrasikan (NOTE: Kebanyakan jenis aset Unity harus "diintegrasikan" ke benang utama. Selama integrasi, awalisasi Aset selesai dan operasi keamanan benang tertentu dilakukan. Ini termasuk panggilan balik skrip, seperti panggilan Awake. Lihat panduan “Resource Management” untuk rincian lebih lanjut.), untuk membatasi dampak pemuatan Aset pada waktu bingkai.

Kedua, setiap asinkron Operasi pemuatan aset, serta setiap permintaan UnityWebRequest, mengembalikan objek AsyncOperation untuk memantau dan mengelola operasi. Objek AsyncOperation ini mengekspos properti priority yang dapat digunakan untuk mengubah prioritas operasi individu.

Akhirnya, www objek, seperti yang dikembalikan dari panggilan ke WWW.LoadFromCacheOrDownload, mengekspos properti Login Login. Penting untuk dicatat bahwa objek WWW tidak secara otomatis menggunakan pengaturan Application.backgroundLoadingPriority sebagai nilai default mereka - objek WWW selalu default ke ThreadPriority.Normal.

Penting untuk dicatat bahwa sistem di bawah tanah yang digunakan untuk menekan dan memuat data berbeda antara API ini. Resources.LoadAsync dan AssetBundle.LoadAssetAsync dioperasikan oleh sistem PreloadManager internal Unity, yang mengatur utas pemuatan sendiri (s) dan melakukan penilaian tingkat sendiri. UnityWebRequest menggunakan kolam benang khususnya sendiri. WWW spawns benang yang sepenuhnya baru setiap kali permintaan dibuat.

Sementara semua mekanisme pemuatan lainnya memiliki sistem queuing bawaan, www tidak. Memanggil WWW.LoadFromCacheOrDownload pada sejumlah besar Aset Terkompresi Memijat sejumlah benang yang setara, yang kemudian bersaing dengan benang utama untuk waktu CPU. Ini dapat dengan mudah mengakibatkan stuttering frame-rate.

Oleh karena itu, ketika menggunakan www untuk memuat dan mendekompresi AssetBundles, dianggap sebagai praktik terbaik untuk menetapkan nilai yang tepat untuk threadPriority dari setiap objek WWW yang diciptakan.

Gerakan objek massa & CullingGroups

Seperti yang disebutkan di bagian Transform Manipulasi, bergerak hierarki Transform besar memiliki biaya CPU yang relatif tinggi karena perbanyakan pesan perubahan. Namun, dalam lingkungan pembangunan nyata, seringkali tidak mungkin untuk runtuh hirarki ke sejumlah sederhana GameObjectsObjek mendasar dalam adegan Unity, yang dapat mewakili karakter, props, pemandangan, kamera, waypoints, dan banyak lagi. Fungsi GameObject didefinisikan oleh Komponen yang melekat padanya. More info
Lihat di Glossary
.

Pada saat yang sama, itu adalah praktik pengembangan yang baik untuk hanya menjalankan perilaku yang cukup untuk mempertahankan kecayaan dunia permainan sambil menghilangkan perilaku pengguna tidak akan memperhatikan - misalnya, dalam Adegan dengan sejumlah besar karakter, selalu lebih optimal untuk hanya menjalankan gerakan Transformasi Mesh-skinning dan animasi-driven untuk karakter yang ada di layar. Tidak ada alasan untuk membuang waktu CPU menghitung elemen visual murni dari simulasi untuk karakter yang off-screen.

Kedua masalah ini dapat ditangani secara rapi dengan API pertama kali diperkenalkan dalam Unity 5.1: CullingGroups.

Alih-alih langsung memanipulasi sekelompok besar GameObjects di tempat kejadian, mengubah sistem untuk memanipulasi parameter vektor3 dari sekelompok BoundingSpheres dalam CullingGroup. Setiap Bounding Sphere berfungsi sebagai repositori otoritatif untuk posisi dunia-space entitas permainan tunggal, dan menerima callback ketika entitas bergerak dekat/dalam frustum dari CullingGroup's main cameraKomponen yang menciptakan gambar sudut pandang tertentu di tempat kejadian Anda. Output ditarik ke layar atau ditangkap sebagai tekstur. More info
Lihat di Glossary
. Panggilan ini kemudian dapat digunakan untuk mengaktifkan / menonaktifkan kode atau komponen (seperti Animators) mengatur perilaku yang hanya harus dijalankan sementara entitas terlihat.

Mengurangi metode memanggil overhead

Perpustakaan string C# menyediakan studi kasus yang sangat baik dalam biaya menambahkan metode tambahan panggilan ke kode perpustakaan sederhana. Dalam bagian pada API string built-in String.StartsWith dan String.EndsWith, disebutkan bahwa penggantian yang dikodekan tangan adalah 10-100 kali lebih cepat daripada metode built-in, bahkan ketika coercion lokal yang tidak diinginkan ditekan.

Alasan utama untuk perbedaan kinerja ini hanya biaya menambahkan metode tambahan panggilan ke loop dalam yang ketat. Setiap metode yang diinvokasi harus menemukan alamat metode dalam memori dan mendorong bingkai lain ke tumpukan. Baik dari operasi ini gratis, tetapi sebagian besar kode mereka cukup kecil untuk mengabaikan.

Namun, ketika menjalankan metode kecil dalam loop ketat, overhead ditambahkan dengan memperkenalkan panggilan metode tambahan dapat menjadi signifikan - dan bahkan dominan.

Pertimbangkan dua metode sederhana berikut.

Example 1:

int Accum { get; set; }
Accum = 0;

for(int i = 0; i < myList.Count; i++) {
    Accum += myList[i];
}

Example 2:

int accum = 0;
int len = myList.Count;

for(int i = 0; i < len; i++) {
    accum += myList[i];
}

Kedua metode menghitung jumlah semua bilangan bulat dalam C # generik List<int>. Contoh pertama adalah sedikit lebih "modern C#" di mana ia menggunakan properti yang dihasilkan secara otomatis untuk menahan nilai datanya.

Sementara di permukaan dua buah kode muncul setara, perbedaannya tidak dapat dianalisis ketika kode dianalisis untuk panggilan metode.

Example 1:

int Accum { get; set; }
Accum = 0;

for(int i = 0;
       i < myList.Count;    // call to List::getCount
       i++) {
    Accum       // call to set_Accum
+=      // call to get_Accum
myList[i];  // call to List::get_Value
}

Jadi ada empat metode memanggil setiap kali loop mengeksekusi:

  • myList.Count memanggil metode get pada properti Count property
  • Metode get dan set pada properti Accum harus disebut
  • get untuk mengambil nilai saat ini Accum sehingga dapat dilewatkan ke operasi tambahan
  • set untuk menetapkan hasil operasi tambahan hingga Accum
  • Operator [] memanggil metode Login Login daftar untuk mengambil nilai item pada indeks tertentu pada daftar.

Example 2:

int accum = 0;
int len = myList.Count;

for(int i = 0;
    i < len; 
    i++) {
    accum += myList[i]; // call to List::get_Value
}

Dalam contoh kedua ini, panggilan ke get_Value tetap, tetapi semua metode lain telah dihilangkan atau tidak lagi mengeksekusi sekali per lingkaran iterasi.

  • Seperti accum sekarang nilai primitif bukan properti, panggilan metode tidak perlu dibuat untuk mengatur atau mengambil nilainya.

  • Sebagai myList.Count diasumsikan untuk tidak bervariasi sementara loop berjalan, aksesnya telah dipindahkan di luar pernyataan kondisi loop, sehingga tidak lagi dieksekusi pada awal setiap iterasi loop.

Waktu untuk dua versi mengungkapkan manfaat sebenarnya dari menghapus 75% metode memanggil overhead dari cuplikan kode tertentu ini. Ketika menjalankan 100.000 kali pada mesin desktop modern:

  • Contoh 1 membutuhkan 324 mili detik untuk melaksanakan
  • Contoh 2 membutuhkan 128 mili detik untuk melaksanakan

Masalah utama di sini adalah bahwa Unity melakukan inlining metode yang sangat sedikit, jika ada. Bahkan di bawah IL2CPPBack-end scripting bersatu yang dapat Anda gunakan sebagai alternatif untuk Mono ketika proyek bangunan untuk beberapa platform. More info
Lihat di Glossary
, banyak metode tidak saat ini masuk dengan benar. Ini terutama berlaku dari properti. Selanjutnya, metode virtual dan antarmuka tidak dapat dilinasi sama sekali.

Oleh karena itu, panggilan metode dinyatakan dalam sumber C# sangat mungkin untuk akhirnya menghasilkan panggilan metode dalam aplikasi biner akhir.

Trivial properti

Unity menyediakan banyak “sederhana” konstan pada jenis datanya untuk kenyamanan pengembang. Namun, dalam cahaya di atas, penting untuk dicatat bahwa konstanta ini umumnya diterapkan sebagai sifat yang mengembalikan nilai-nilai konstan.

Tubuh properti vektor3.zero adalah sebagai berikut:

get { return new Vector3(0,0,0); }

Quaternion.identitas sangat mirip:

get { return new Quaternion(0,0,0,1); }

Sementara biaya mengakses properti ini biasanya kecil dibandingkan dengan kode sebenarnya di sekitarnya, mereka dapat membuat perbedaan kecil ketika mereka dieksekusi ribuan kali per bingkai (atau lebih).

Untuk jenis primitif sederhana, gunakan nilai const sebagai gantinya. Nilai Const dilinasi pada waktu kompilasi - referensi ke variabel const diganti dengan nilainya.

Sitemap Karena setiap referensi ke variabel Note: diganti dengan nilainya, tidak dapat diinformasikan untuk menyatakan string panjang atau jenis data besar lainnya const. Ini tidak perlu bloat ukuran biner akhir karena semua data duplicated dalam kode instruksi akhir.const. This unnecessarily bloats the size of the final binary due to all the duplicated data in the final instruction code.

Dimanapun const tidak tepat, buat variabel static readonly bukan. Dalam beberapa proyek, bahkan properti sepele bawaan Unity telah diganti dengan variabel static readonly, menghasilkan peningkatan kecil dalam kinerja.

Trivial metode

Metode trivial lebih rumit. Sangat berguna untuk dapat menyatakan fungsi sekali dan menggunakannya di tempat lain. Namun, dalam lingkaran bagian dalam yang ketat, mungkin perlu untuk berangkat dari praktik pengkodean yang baik dan bukan kode tertentu “secara umum”.

Beberapa metode dapat dihilangkan langsung. Pertimbangkan Quaternion.Set, Transform.Translate atau Vector3.Scale. Ini melakukan operasi yang sangat sepele dan dapat diganti dengan pernyataan tugas sederhana.

Untuk metode yang lebih kompleks, beratkan bukti profil untuk inlining manual terhadap biaya jangka panjang menjaga kode yang lebih sesuai.

Optimasi Umum
Aset memuat metrik