Mengaktifkan koleksi sampah
Profiler

Pengumpulan sampah praktik terbaik

Photogallery otomatis, tetapi proses membutuhkan waktu CPU yang signifikan.

Manajemen memori otomatis C# mengurangi risiko kebocoran memori dan kesalahan pemrograman lainnya, dibandingkan dengan bahasa pemrograman lainnya seperti C++, di mana Anda harus melacak secara manual dan membebaskan semua memori yang Anda temukan.

Manajemen memori otomatis memungkinkan Anda untuk menulis kode dengan cepat dan mudah, dan dengan beberapa kesalahan. Namun, kenyamanan ini mungkin memiliki implikasi kinerja. Untuk mengoptimalkan kode Anda untuk kinerja, Anda harus menghindari situasi di mana aplikasi Anda memicu kolektor sampah banyak. Bagian ini menguraikan beberapa masalah umum dan alur kerja yang mempengaruhi ketika aplikasi Anda memicu kolektor sampah.

Alokasi sementara

Ini umum untuk aplikasi untuk mengalokasikan data sementara ke managed heap dalam setiap bingkai; namun, ini dapat mempengaruhi kinerja aplikasi. Contoh:

  • Jika program mengalokasikan satu kilobyte (1KB) memori sementara setiap bingkai, dan berjalan pada 60 frames per secondFrekuensi di mana bingkai berturut-turut ditampilkan dalam permainan berjalan. More info
    Lihat di Glossary
    , maka harus mengalokasikan 60 kilobyte memori sementara per detik. Selama satu menit, ini menambah hingga 3.6 megabyte memori yang tersedia untuk kolektor sampah.
  • Mencapai kolektor sampah sekali per detik memiliki efek negatif pada kinerja. Jika kolektor sampah hanya berjalan sekali per menit, itu harus membersihkan 3,6 megabyte tersebar di ribuan alokasi individu, yang mungkin mengakibatkan waktu pengumpulan sampah yang signifikan.
  • Memuat operasi memiliki dampak pada kinerja. Jika aplikasi Anda menghasilkan banyak benda sementara selama operasi beban aset berat, dan referensi Unity objek-objek tersebut sampai operasi selesai, maka kolektor sampah tidak dapat melepaskan benda-benda sementara tersebut. Ini berarti bahwa landak yang berhasil perlu berkembang, meskipun Unity melepaskan banyak objek yang mengandung waktu singkat kemudian.

Untuk mendapatkan sekitar ini, Anda harus mencoba untuk mengurangi jumlah alokasi heap yang sering dikelola mungkin: ideal untuk 0 byte per bingkai, atau dekat dengan nol karena Anda bisa mendapatkan.

Kolam objek yang dapat digunakan kembali

Ada banyak kasus di mana Anda dapat mengurangi jumlah kali aplikasi Anda menciptakan dan menghancurkan objek, untuk menghindari menghasilkan sampah. Ada jenis objek tertentu dalam permainan, seperti proyektil, yang mungkin muncul dan lagi meskipun hanya sejumlah kecil yang pernah bermain sekaligus. Dalam kasus seperti ini, Anda dapat menggunakan kembali objek, daripada menghancurkan yang lama dan menggantinya dengan yang baru.

Sebagai contoh, tidak optimal untuk mengulang objek proyektil baru dari PrefabJenis 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
setiap kali satu dipecat. Alih-alih, Anda dapat menghitung jumlah proyektil maksimum yang pernah ada secara bersamaan selama gameplay, dan meluruskan array objek dengan ukuran yang benar ketika permainan pertama memasuki gameplay 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
. Untuk melakukan ini:

  • Mulai dengan semua set proyektil 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
    untuk tidak aktif.
  • Ketika proyektil dipecat, cari melalui array untuk menemukan proyektil inaktif pertama di array, pindahkan ke posisi yang diperlukan dan atur GameObject untuk aktif.
  • Ketika proyektil hancur, atur GameObject untuk tidak aktif lagi.

Anda dapat menggunakan kelas ObjectPool, yang memberikan implementasi teknik kolam objek yang dapat digunakan kembali ini.

Kode di bawah ini menunjukkan implementasi sederhana dari kolam objek berbasis stack. Anda mungkin merasa berguna untuk merujuk apakah Anda menggunakan versi Unity yang lebih tua yang tidak mengandung API ObjectPool, atau jika Anda ingin melihat contoh bagaimana kolam objek kustom mungkin diterapkan.

using System.Collections.Generic;
using UnityEngine;

public class ExampleObjectPool : MonoBehaviour {

   public GameObject PrefabToPool;
   public int MaxPoolSize = 10;
  
   private Stack<GameObject> inactiveObjects = new Stack<GameObject>();
  
   void Start() {
       if (PrefabToPool != null) {
           for (int i = 0; i < MaxPoolSize; ++i) {
               var newObj = Instantiate(PrefabToPool);
               newObj.SetActive(false);
               inactiveObjects.Push(newObj);
           }
       }
   }

   public GameObject GetObjectFromPool() {
       while (inactiveObjects.Count > 0) {
           var obj = inactiveObjects.Pop();
          
           if (obj != null) {
               obj.SetActive(true);
               return obj;
           }
           else {
               Debug.LogWarning("Found a null object in the pool. Has some code outside the pool destroyed it?");
           }
       }
      
       Debug.LogError("All pooled objects are already in use or have been destroyed");
       return null;
   }
  
   public void ReturnObjectToPool(GameObject objectToDeactivate) {
       if (objectToDeactivate != null) {
           objectToDeactivate.SetActive(false);
           inactiveObjects.Push(objectToDeactivate);
       }
   }
}

WordPress.org

String di C# adalah tipe referensi immutableAnda tidak dapat mengubah isi paket immutable (read-only). Ini adalah kebalikan dari mutable. Kebanyakan paket tidak dapat diakses, termasuk paket yang diunduh dari registry paket atau melalui URL Git.
Lihat di Glossary
. Jenis referensi berarti bahwa Unity mengalokasikan mereka di tumpukan yang berhasil dan tunduk pada pengumpulan sampah. Cara yang tidak diinginkan yang pernah dibuat string, tidak dapat diubah; setiap upaya untuk memodifikasi hasil string dalam string yang sama sekali baru. Untuk alasan ini, Anda harus menghindari membuat string sementara di mana pun mungkin.

Pertimbangkan kode contoh berikut, yang menggabungkan array string menjadi string tunggal. Setiap kali string baru ditambahkan di dalam lingkaran, konten sebelumnya dari variabel hasil menjadi redundan, dan kode mengalokasikan seluruh string baru.

// Bad C# script example: repeated string concatenations create lots of
// temporary strings.
using UnityEngine;

public class ExampleScript : MonoBehaviour {
    string ConcatExample(string[] stringArray) {
        string result = "";

        for (int i = 0; i < stringArray.Length; i++) {
            result += stringArray[i];
        }

        return result;
    }

}

Jika stringArray input mengandung { “A”, “B”, “C”, “D”, “E” }, metode ini menghasilkan penyimpanan pada heap untuk string berikut:

  • “A”
  • “AB”
  • “ABC”
  • “ABCD”
  • “ABCDE”

Dalam contoh ini, Anda hanya perlu string akhir, dan yang lain adalah alokasi redundan. Semakin banyak item yang ada di array input, semakin banyak string metode ini menghasilkan, setiap lebih lama dari yang terakhir.

Jika Anda perlu untuk membuat banyak string bersama-sama maka Anda harus menggunakan kelas System.Text.StringBuilder mono perpustakaan. Versi yang lebih baik dari script di atas terlihat seperti ini:

// Good C# script example: StringBuilder avoids creating temporary strings,
// and only allocates heap memory for the final result string.
using UnityEngine;
using System.Text;

public class ExampleScript : MonoBehaviour {
    private StringBuilder _sb = new StringBuilder(16);

    string ConcatExample(string[] stringArray) {
        _sb.Clear();

        for (int i = 0; i < stringArray.Length; i++) {
            _sb.Append(stringArray[i]);
        }

        return _sb.ToString();
    }
}

Kondensasi berulang tidak mengurangi kinerja terlalu banyak kecuali itu disebut sering, seperti pada setiap pembaruan bingkai. Contoh berikut mengalokasikan string baru setiap kali Memperbarui disebut, dan menghasilkan aliran objek yang terus menerus yang kolektor sampah harus ditangani:

// Bad C# script example: Converting the score value to a string every frame
// and concatenating it with “Score: “ generates strings every frame.
using UnityEngine;
using UnityEngine.UI;

public class ExampleScript : MonoBehaviour {
    public Text scoreBoard;
    public int score;
    
    void Update() {
        string scoreText = "Score: " + score.ToString();
        scoreBoard.text = scoreText;
    }
}

Untuk mencegah persyaratan berkelanjutan ini untuk pengumpulan sampah, Anda dapat mengkonfigurasi kode sehingga teks hanya memperbarui ketika skor berubah:

// Better C# script example: the score conversion is only performed when the
// score has changed
using UnityEngine;
using UnityEngine.UI;

public class ExampleScript : MonoBehaviour {
    public Text scoreBoard;
    public string scoreText;
    public int score;
    public int oldScore;
    
    void Update() {
        if (score != oldScore) {
            scoreText = "Score: " + score.ToString();
            scoreBoard.text = scoreText;
            oldScore = score;
        }
    }
}

Untuk meningkatkan lebih lanjut ini, Anda dapat menyimpan judul skor (bagian yang mengatakan “Score: ”) dan tampilan skor dalam dua objek UI.Text yang berbeda, yang berarti bahwa tidak perlu untuk komputasi string. Kode harus tetap mengubah nilai skor menjadi string, tetapi ini adalah peningkatan versi sebelumnya:

// Best C# script example: the score conversion is only performed when the
// score has changed, and the string concatenation has been removed
using UnityEngine;
using UnityEngine.UI;

public class ExampleScript : MonoBehaviour {
   public Text scoreBoardTitle;
   public Text scoreBoardDisplay;
   public string scoreText;
   public int score;
   public int oldScore;

   void Start() {
       scoreBoardTitle.text = "Score: ";
   }

   void Update() {
       if (score != oldScore) {
           scoreText = score.ToString();
           scoreBoardDisplay.text = scoreText;
           oldScore = score;
       }
   }
}

Metode mengembalikan nilai array

Kadang-kadang mungkin nyaman untuk menulis metode yang menciptakan array baru, mengisi array dengan nilai dan kemudian mengembalikannya. Namun, jika metode ini disebut berulang kali, maka memori baru dialokasikan setiap kali.

Kode contoh berikut menunjukkan contoh metode yang membuat array setiap kali disebut:

// Bad C# script example: Every time the RandomList method is called it
// allocates a new array
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    float[] RandomList(int numElements) {
        var result = new float[numElements];
        
        for (int i = 0; i < numElements; i++) {
            result[i] = Random.value;
        }
        
        return result;
    }
}

Satu cara Anda dapat menghindari mengalokasikan memori setiap kali adalah untuk menggunakan fakta bahwa array adalah jenis referensi. Anda dapat mengubah array yang dilewati menjadi metode sebagai parameter, dan hasilnya tetap setelah metode kembali. Untuk melakukan ini, Anda dapat mengkonfigurasi kode contoh sebagai berikut:

// Good C# script example: This version of method is passed an array to fill
// with random values. The array can be cached and re-used to avoid repeated
// temporary allocations
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    void RandomList(float[] arrayToFill) {
        for (int i = 0; i < arrayToFill.Length; i++) {
            arrayToFill[i] = Random.value;
        }
    }
}

Kode ini menggantikan konten yang ada dari array dengan nilai-nilai baru. Aliran kerja ini membutuhkan kode panggilan untuk melakukan alokasi awal array, tetapi fungsi tidak menghasilkan sampah baru ketika disebut. array kemudian dapat digunakan kembali dan diisi ulang dengan nomor acak metode ini disebut tanpa alokasi baru di landak yang dikelola.

Koleksi dan penggunaan array

Ketika Anda menggunakan array atau kelas dari ruang nama System.Collection (misalnya, Daftar atau Dictionaries), itu efisien untuk digunakan kembali atau kolam koleksi atau array yang dialokasikan. Kelas koleksi mengekspos metode yang jelas, yang menghilangkan nilai koleksi tetapi tidak melepaskan memori yang dialokasikan ke koleksi.

Ini berguna jika Anda ingin mengalokasikan koleksi “bantuer” sementara untuk komputasi kompleks. Contoh kode berikut menunjukkan ini:

// Bad C# script example. This Update method allocates a new List every frame.
void Update() {

    List<float> nearestNeighbors = new List<float>();

    findDistancesToNearestNeighbors(nearestNeighbors);

    nearestNeighbors.Sort();

    // … use the sorted list somehow …
}

Contoh ini mengalokasikan daftar terdekatNeighbors sekali per frame untuk mengumpulkan satu set poin data.

Anda dapat mengangkat Daftar ini dari metode dan ke dalam kelas yang mengandung, sehingga kode Anda tidak perlu mengalokasikan Daftar baru setiap bingkai:

// Good C# script example. This method re-uses the same List every frame.
List<float> m_NearestNeighbors = new List<float>();

void Update() {

    m_NearestNeighbors.Clear();

    findDistancesToNearestNeighbors(NearestNeighbors);

    m_NearestNeighbors.Sort();

    // … use the sorted list somehow …
}

Kode contoh ini mempertahankan dan menggunakan memori Daftar di beberapa bingkai. Kode hanya mengalokasikan memori baru ketika Daftar perlu diperluas.

Penutupan dan metode anonim

Secara umum, Anda harus menghindari penutupan di C# setiap saat mungkin. Anda harus meminimalkan penggunaan metode anonim dan referensi metode dalam kode sensitif kinerja, dan terutama dalam kode yang mengeksekusi dasar per-frame.

Metode referensi dalam C # adalah jenis referensi, sehingga mereka dialokasikan pada tuju. Ini berarti bahwa jika Anda melewati referensi metode sebagai argumen, mudah untuk membuat alokasi sementara. Alokasi ini terjadi terlepas dari apakah metode yang Anda lewatkan adalah metode anonim atau yang ditentukan.

Juga, ketika Anda mengonversi metode anonim ke penutupan, jumlah memori yang diperlukan untuk melewati penutupan ke metode meningkatkan banyak.

Berikut ini sampel kode di mana daftar nomor acak harus diurutkan dalam urutan tertentu. Ini menggunakan metode anonim untuk mengontrol urutan penyortiran dari daftar, dan penyortiran tidak membuat alokasi.

// Good C# script example: using an anonymous method to sort a list. 
// This sorting method doesn’t create garbage
List<float> listOfNumbers = getListOfRandomNumbers();


listOfNumbers.Sort( (x, y) =>

(int)x.CompareTo((int)(y/2)) 

);

Untuk membuat cuplikan ini dapat digunakan kembali, Anda mungkin mengganti 2 konstan untuk variabel dalam lingkup lokal:

// Bad C# script example: the anonymous method has become a closure,
// and now allocates memory to store the value of desiredDivisor
// every time it is called.
List<float> listOfNumbers = getListOfRandomNumbers();


int desiredDivisor = getDesiredDivisor();

listOfNumbers.Sort( (x, y) =>

(int)x.CompareTo((int)(y/desiredDivisor))

);

Metode anonim sekarang perlu mengakses keadaan variabel yang berada di luar ruang lingkupnya, dan sehingga metode telah menjadi penutupan. Variabel desiredDivisor harus dilewatkan ke dalam penutupan sehingga kode penutupan dapat menggunakannya.

Untuk memastikan bahwa nilai yang benar dilewatkan pada penutupan, C# menghasilkan kelas anonim yang dapat mempertahankan variabel lingkup eksternal yang kebutuhan penutupan. Salinan kelas ini sesaat ketika penutupan dilewatkan ke metode Sort, dan salinan diinisialisasi dengan nilai integer Divisor yang diinginkan.

Mengeksekusi penutupan memerlukan instansi salinan kelas yang dihasilkan, dan semua kelas adalah jenis referensi dalam C #. Untuk alasan ini, mengeksekusi penutupan membutuhkan alokasi objek pada heap yang dikelola.

Boxing

Tinju adalah salah satu sumber yang paling umum dari alokasi memori sementara yang tidak diinginkan yang ditemukan di proyek Unity. Kebetulan ketika variabel bertipe nilai akan secara otomatis dikonversi ke tipe referensi. Ini paling sering terjadi ketika melewati variabel berjenis nilai primitif (seperti int dan float) ke metode berjenis objek. Anda harus menghindari tinju saat menulis kode C# untuk Unity.

Dalam contoh ini, integer di x kotak sehingga dapat dilewatkan ke metode object.Equals, karena metode Equals pada objek membutuhkan bahwa objek dilewatkan ke dalamnya.

int x = 1;

object y = new object();

y.Equals(x);

C# IDE dan kompiler tidak mengeluarkan peringatan tentang tinju, meskipun tinju mengarah ke alokasi memori yang tidak diinginkan. Hal ini karena C# mengasumsikan bahwa alokasi sementara kecil efisien ditangani oleh kolektor garbage generasi dan kolam memori sensitif ukuran alokasi.

Meskipun allokator Unity tidak menggunakan kolam memori yang berbeda untuk alokasi kecil dan besar, kolektor sampah Unity tidak generasi, sehingga tidak dapat menyapu secara efisien dari alokasi sementara kecil yang menghasilkan kotak.

Mengidentifikasi tinju

Tinju muncul di jejak CPU sebagai panggilan ke salah satu dari beberapa metode, tergantung pada ujung belakang scripting digunakan. Ini mengambil salah satu bentuk berikut, di mana <example class> adalah nama kelas atau merusak, dan adalah sejumlah argumen:

<example class>::Box(…)
Box(…)
<example class>_Box(…)

Untuk menemukan tinju, Anda juga dapat mencari output dari penampil atau pemirsa IL, seperti IL alat pemirsa yang dibangun ke ReSharper atau dotPeek decompiler. Instruksi IL adalah box.

API Unity

Penyebab halus dari array alokasi yang tidak diinginkan adalah akses berulang dari API Unity yang kembali array. Semua API Unity yang mengembalikan array menciptakan salinan baru dari array setiap kali mereka diakses. Jika kode Anda mengakses API Unity bernilai array lebih sering daripada yang diperlukan, kemungkinan akan menjadi dampak merugikan pada kinerja.

Sebagai contoh, kode berikut tidak perlu membuat empat salinan array vertices per iterasi loop. Alokasi terjadi setiap kali properti .vertices diakses.

// Bad C# script example: this loop create 4 copies of the vertices array per iteration
void Update() {
    for(int i = 0; i < mesh.vertices.Length; i++) {
        float x, y, z;

        x = mesh.vertices[i].x;
        y = mesh.vertices[i].y;
        z = mesh.vertices[i].z;

        // ...

        DoSomething(x, y, z);   
    }
}

Anda dapat mengisi ulang kode ini menjadi alokasi array tunggal, terlepas dari jumlah iterasi loop. Untuk melakukan ini, mengkonfigurasi kode Anda untuk menangkap array vertices sebelum loop:

// Better C# script example: create one copy of the vertices array
// and work with that
void Update() {
    var vertices = mesh.vertices;

    for(int i = 0; i < vertices.Length; i++) {

        float x, y, z;

        x = vertices[i].x;
        y = vertices[i].y;
        z = vertices[i].z;

        // ...

        DoSomething(x, y, z);   
    }
}

Cara yang lebih baik untuk melakukan ini adalah untuk mempertahankan Daftar vertik yang cache dan digunakan kembali antara bingkai, dan kemudian gunakan Mesh.GetVertices untuk mengisinya ketika diperlukan.

// Best C# script example: create one copy of the vertices array
// and work with that.
List<Vector3> m_vertices = new List<Vector3>();

void Update() {
    mesh.GetVertices(m_vertices);

    for(int i = 0; i < m_vertices.Length; i++) {

        float x, y, z;

        x = m_vertices[i].x;
        y = m_vertices[i].y;
        z = m_vertices[i].z;

        // ...

        DoSomething(x, y, z);   
    }
}

Sementara implikasi kinerja CPU mengakses properti sekali tidak tinggi, akses berulang dalam loop ketat menciptakan hotspot kinerja CPU. Akses berulang memperluas managed heap.

Masalah ini umum pada perangkat mobile, karena API Input.touches berperilaku mirip dengan di atas. Ini juga umum untuk proyek untuk mengandung kode yang mirip dengan berikut, di mana alokasi terjadi setiap kali properti .touches diakses.

// Bad C# script example: Input.touches returns an array every time it’s accessed
for ( int i = 0; i < Input.touches.Length; i++ ) {
   Touch touch = Input.touches[i];

    // …
}

Untuk meningkatkan ini, Anda dapat mengkonfigurasi kode Anda untuk mengangkat alokasi array dari kondisi loop:

// Better C# script example: Input.touches is only accessed once here
Touch[] touches = Input.touches;

for ( int i = 0; i < touches.Length; i++ ) {

   Touch touch = touches[i];

   // …
}

Contoh kode berikut mengkonversi contoh sebelumnya ke API Sentuh bebas alokasi:

// BEST C# script example: Input.touchCount and Input.GetTouch don’t allocate at all.
int touchCount = Input.touchCount;

for ( int i = 0; i < touchCount; i++ ) {
   Touch touch = Input.GetTouch(i);

   // …
}

Sitemap Akses properti (Note:) tetap berada di luar kondisi lingkaran, untuk menghemat dampak CPU untuk memanggil metode mendapatkan properti.Input.touchCount) remains outside the loop condition, to save the CPU impact of invoking the property’s get method.

Alternatif non-allocating API

Beberapa Unity API memiliki versi alternatif yang tidak menyebabkan alokasi memori. Anda harus menggunakan ini ketika mungkin. Meme it Tabel berikut menunjukkan pilihan kecil dari API allokating umum dan alternatif non-penemuan mereka. Daftar tidak knalpot, tetapi harus menunjukkan jenis API untuk menonton.

Allocating API Non-allocating API alternative
Fisika.RaycastSemua Fisika.RaycastNonAlloc
Animator.parameter Animator.parameterCount dan Animator.GetParameter
Renderer.sharedMaterials Renderer.GetSharedMaterials

Reuse array kosong

Beberapa tim pengembangan lebih memilih untuk mengembalikan array kosong daripada null ketika metode nilai array perlu mengembalikan set kosong. Pola pengkodean ini umum dalam banyak bahasa yang dikelola, terutama C# dan Java.

Secara umum, ketika mengembalikan array nol dari metode, lebih efisien untuk mengembalikan instance statis pra-dilokasi dari array nol-panjang daripada berulang kali menciptakan array kosong.

Sumber daya

Mengaktifkan koleksi sampah
Profiler