Menyelesaikan LMK dalam game Unity Anda adalah proses yang sistematis:

Mendapatkan cuplikan memori
Gunakan Unity Profiler untuk mendapatkan cuplikan memori yang dikelola Unity. Gambar 2 menunjukkan lapisan pengelolaan memori yang digunakan Unity untuk menangani memori dalam game Anda.

Memori terkelola
Pengelolaan memori Unity menerapkan lapisan memori yang dikontrol yang menggunakan heap terkelola dan pengumpul sampah untuk mengalokasikan dan menetapkan memori secara otomatis. Sistem memori terkelola adalah lingkungan skrip C# berdasarkan Mono atau IL2CPP. Manfaat sistem memori terkelola adalah sistem ini menggunakan pembersih sampah memori untuk membebaskan alokasi memori secara otomatis.
Memori tidak terkelola C#
Lapisan memori C# yang tidak dikelola menyediakan akses ke lapisan memori native, sehingga memungkinkan kontrol yang tepat atas alokasi memori saat menggunakan kode C#. Lapisan pengelolaan memori ini dapat diakses melalui namespace Unity.Collections dan oleh fungsi seperti UnsafeUtility.Malloc dan UnsafeUtility.Free.
Memori native
Inti C/C++ internal Unity menggunakan sistem memori native untuk mengelola adegan, aset, API grafis, driver, subsistem, dan buffer plug-in. Meskipun akses langsung dibatasi, Anda dapat memanipulasi data dengan aman menggunakan C# API Unity dan memanfaatkan kode native yang efisien. Memori native jarang memerlukan interaksi langsung, tetapi Anda dapat memantau dampak memori native terhadap performa menggunakan Profiler dan menyesuaikan setelan untuk mengoptimalkan performa.
Memori tidak dibagikan antara C# dan kode native seperti yang ditunjukkan pada gambar 3. Data yang diperlukan oleh C# dialokasikan di ruang memori terkelola setiap kali diperlukan.
Agar kode game terkelola (C#) dapat mengakses data memori native mesin, misalnya, panggilan ke GameObject.transform membuat panggilan native untuk mengakses data memori di area native, lalu menampilkan nilai ke C# menggunakan Bindings. Binding memastikan konvensi panggilan yang tepat untuk setiap platform dan menangani marshalling otomatis jenis terkelola ke dalam jenis native yang setara.
Hal ini hanya terjadi pada saat pertama kali, karena managed shell untuk mengakses properti transform dipertahankan dalam kode native. Meng-cache properti transform dapat mengurangi jumlah panggilan bolak-balik antara kode terkelola dan native, tetapi kegunaan caching bergantung pada seberapa sering properti digunakan. Selain itu, perhatikan bahwa Unity tidak menyalin bagian memori native ke memori terkelola saat Anda mengakses API ini.

Untuk mempelajari lebih lanjut, lihat Pengantar memori di Unity.
Selain itu, menetapkan anggaran memori sangat penting agar game Anda berjalan dengan lancar, dan menerapkan sistem pelaporan atau analisis penggunaan memori memastikan bahwa setiap rilis baru tidak melebihi anggaran memori. Mengintegrasikan pengujian Mode Play dengan continuous integration (CI) untuk memverifikasi penggunaan memori di area tertentu dalam game adalah strategi lain untuk mendapatkan insight yang lebih baik.
Mengelola aset
Ini adalah bagian yang paling berdampak dan dapat ditindaklanjuti dari konsumsi memori. Profil sesegera mungkin.
Penggunaan memori dalam game Android dapat sangat bervariasi, bergantung pada jenis game, jumlah dan jenis aset, serta strategi pengoptimalan memori. Namun, kontributor umum penggunaan memori biasanya mencakup tekstur, mesh, file audio, shader, animasi, dan skrip.
Mendeteksi aset duplikat
Langkah pertama adalah mendeteksi aset yang dikonfigurasi dengan buruk dan aset duplikat menggunakan profiler memori, alat laporan build, atau Project Auditor.
Tekstur
Analisis dukungan perangkat game Anda dan tentukan format tekstur yang tepat. Anda dapat memisahkan paket tekstur untuk perangkat kelas atas dan kelas bawah menggunakan Play Asset Delivery, Addressable, atau proses yang lebih manual dengan AssetBundle.
Ikuti rekomendasi paling terkenal yang tersedia di Mengoptimalkan Performa Game Seluler Anda dan di postingan diskusi Mengoptimalkan Setelan Impor Tekstur Unity. Kemudian coba solusi berikut:
Kompres tekstur dengan format ASTC untuk mengurangi jejak memori dan bereksperimen dengan kecepatan blok yang lebih tinggi, seperti 8x8.
Jika penggunaan ETC2 diperlukan, kemas tekstur Anda dalam Atlas. Menempatkan beberapa tekstur ke dalam satu tekstur memastikan Power of Two (POT), dapat mengurangi panggilan gambar, dan dapat mempercepat rendering.
Mengoptimalkan format dan ukuran tekstur RenderTarget. Hindari tekstur resolusi tinggi yang tidak perlu. Menggunakan tekstur yang lebih kecil di perangkat seluler akan menghemat memori.
Gunakan Pengemasan saluran tekstur untuk menghemat memori tekstur.
Mesh dan model
Mulai dengan memeriksa setelan dasar (halaman 27) dan verifikasi setelan impor mesh berikut:
- Gabungkan mesh yang berlebihan dan lebih kecil.
- Kurangi jumlah verteks untuk objek dalam adegan (misalnya, objek statis atau objek yang jauh).
- Buat grup Level Detail (LOD) untuk aset geometri tinggi.
Material dan shader
- Menghapus varian shader yang tidak digunakan secara terprogram selama proses build.
- Gabungkan varian shader yang sering digunakan ke dalam shader uber untuk menghindari duplikasi shader.
- Aktifkan pemuatan shader dinamis untuk mengatasi footprint memori besar dari shader yang dimuat sebelumnya di VRAM/RAM. Namun, perhatikan jika kompilasi shader menyebabkan gangguan frame.
- Gunakan pemuatan shader dinamis untuk mencegah semua varian dimuat. Untuk mengetahui informasi selengkapnya, lihat postingan blog Peningkatan waktu build shader dan penggunaan memori.
- Gunakan instancing materi dengan benar dengan memanfaatkan
MaterialPropertyBlocks
.
Audio
Mulai dengan memeriksa setelan dasar (halaman 41), dan verifikasi setelan impor mesh berikut:
- Hapus referensi
AudioClip
yang tidak digunakan atau berlebihan saat menggunakan mesin audio pihak ketiga seperti FMOD atau Wwise. - Memuat data audio terlebih dahulu. Nonaktifkan pramuat untuk klip yang tidak segera diperlukan selama runtime atau startup adegan. Hal ini membantu mengurangi overhead memori selama inisialisasi adegan.
Animasi
- Sesuaikan setelan kompresi animasi Unity untuk meminimalkan
jumlah keyframe dan menghilangkan data yang berlebihan.
- Pengurangan frame utama: Menghapus frame utama yang tidak perlu secara otomatis
- Kompresi kuaternion: Mengompresi data rotasi untuk mengurangi penggunaan memori
Anda dapat menyesuaikan setelan kompresi di Setelan Impor Animasi di bagian tab Rig atau Animasi.
Gunakan kembali klip animasi, bukan menduplikasi klip animasi untuk objek yang berbeda.
Gunakan Pengontrol Penggantian Animator untuk menggunakan kembali Pengontrol Animator dan mengganti klip tertentu untuk karakter yang berbeda.
Membuat animasi berbasis fisika: Jika animasi Anda didorong oleh fisika atau prosedural, buat animasi tersebut menjadi klip animasi untuk menghindari penghitungan runtime.
Mengoptimalkan rig kerangka: Gunakan lebih sedikit tulang dalam rig untuk mengurangi kompleksitas dan konsumsi memori.
- Hindari tulang berlebihan untuk objek kecil atau statis.
- Jika tulang tertentu tidak dianimasikan atau diperlukan, hapus tulang tersebut dari rig.
Mengurangi durasi klip animasi.
- Pangkas klip animasi untuk hanya menyertakan frame yang diperlukan. Hindari menyimpan animasi yang tidak digunakan atau terlalu panjang.
- Gunakan animasi berulang, bukan membuat klip panjang untuk gerakan berulang.
Pastikan hanya satu komponen animasi yang dilampirkan atau diaktifkan. Misalnya, nonaktifkan atau hapus komponen Animasi lama jika Anda menggunakan Animator.
Hindari penggunaan Animator jika tidak diperlukan. Untuk VFX sederhana, gunakan library tweening atau terapkan efek visual dalam skrip. Sistem animator dapat menggunakan banyak resource, terutama pada perangkat seluler kelas bawah.
Gunakan Sistem Job untuk animasi saat menangani sejumlah besar animasi, karena sistem tersebut telah sepenuhnya didesain ulang agar lebih efisien dalam penggunaan memori.
Suasana
Saat adegan baru dimuat, adegan tersebut akan memuat aset sebagai dependensi. Namun, tanpa pengelolaan siklus proses aset yang tepat, dependensi ini tidak dipantau oleh penghitung referensi. Akibatnya, aset dapat tetap berada dalam memori meskipun setelah adegan yang tidak digunakan dibongkar, sehingga menyebabkan fragmentasi memori.
- Gunakan Penggabungan Objek Unity untuk menggunakan kembali instance GameObject untuk elemen gameplay berulang karena penggabungan objek menggunakan stack untuk menyimpan kumpulan instance objek untuk digunakan kembali dan tidak aman untuk thread. Meminimalkan
Instantiate
danDestroy
akan meningkatkan performa CPU dan stabilitas memori. - Membongkar aset:
- Lepaskan aset secara strategis selama momen yang kurang penting, seperti layar pembuka atau layar pemuatan.
- Penggunaan
Resources.UnloadUnusedAssets
yang sering menyebabkan lonjakan dalam pemrosesan CPU karena operasi pemantauan dependensi internal yang besar. - Periksa lonjakan CPU besar di penanda profil GC.MarkDependencies.
Hapus atau kurangi frekuensi eksekusinya, dan hapus muatan resource tertentu secara manual menggunakan Resources.UnloadAsset, bukan mengandalkan
Resources.UnloadUnusedAssets()
yang mencakup semua.
- Menyusun ulang adegan, bukan terus-menerus menggunakan Resources.UnloadUnusedAssets.
- Memanggil
Resources.UnloadUnusedAssets()
untukAddressables
dapat secara tidak sengaja membatalkan pemuatan paket yang dimuat secara dinamis. Kelola siklus proses aset yang dimuat secara dinamis dengan cermat.
Lain-lain
Fragmentasi yang disebabkan oleh transisi adegan — Saat metode
Resources.UnloadUnusedAssets()
dipanggil, Unity akan melakukan hal berikut:- Mengosongkan memori untuk aset yang tidak lagi digunakan
- Menjalankan operasi seperti pengumpul sampah untuk memeriksa heap objek native dan terkelola untuk aset yang tidak digunakan dan membongkarnya
- Membersihkan memori tekstur, mesh, dan aset asalkan tidak ada referensi aktif
AssetBundle
atauAddressable
- melakukan perubahan di area ini sangat rumit dan memerlukan upaya kolektif dari tim untuk menerapkan strategi. Namun, setelah dikuasai, strategi ini akan meningkatkan penggunaan memori secara signifikan, mengurangi ukuran download, dan menurunkan biaya cloud. Untuk mengetahui informasi selengkapnya tentang pengelolaan aset di Unity dengan, lihatAddressables
.Dependensi bersama yang terpusat &mdash: Kelompokkan dependensi bersama, seperti shader, tekstur, dan font, secara sistematis ke dalam bundle atau grup
Addressable
khusus. Hal ini mengurangi duplikasi dan memastikan bahwa aset yang tidak perlu diturunkan secara efisien.Gunakan
Addressables
untuk pelacakan dependensi - Addressables mempermudah pemuatan dan pembongkaran dapat secara otomatis membongkar dependensi yang tidak lagi dirujuk. Beralih keAddressables
untuk pengelolaan konten dan penyelesaian dependensi mungkin merupakan solusi yang layak, bergantung pada kasus spesifik game. Analisis rantai dependensi dengan alat Analyze untuk mengidentifikasi duplikat atau dependensi yang tidak perlu. Atau, lihat Alat Data Unity jika Anda menggunakan AssetBundle.TypeTrees
- jikaAddressables
danAssetBundles
game Anda dibuat dan di-deploy menggunakan versi Unity yang sama dengan pemain dan tidak memerlukan kompatibilitas mundur dengan build pemain lain, pertimbangkan untuk menonaktifkan penulisanTypeTree
, yang akan mengurangi ukuran paket dan jejak memori objek file yang diserialkan. Ubah proses build di setelan paket Addressables lokal ContentBuildFlags ke DisableWriteTypeTree.
Menulis kode yang kompatibel dengan pengumpul sampah
Unity menggunakan pengumpulan sampah (GC) untuk mengelola memori dengan mengidentifikasi dan membebaskan memori yang tidak digunakan secara otomatis. Meskipun GC sangat penting, GC dapat menyebabkan masalah performa (misalnya, lonjakan kecepatan frame) jika tidak ditangani dengan benar, karena proses ini dapat menjeda game sejenak, sehingga menyebabkan gangguan performa dan pengalaman pengguna yang tidak optimal.
Lihat manual Unity untuk mengetahui teknik berguna dalam mengurangi frekuensi alokasi heap terkelola dan UnityPerformanceTuningBible, halaman 271, untuk mengetahui contohnya.
Mengurangi alokasi pengumpul sampah:
- Hindari LINQ, lambda, dan penutupan, yang mengalokasikan memori heap.
- Gunakan
StringBuilder
untuk string yang dapat diubah, bukan penggabungan string. - Gunakan kembali koleksi dengan memanggil
COLLECTIONS.Clear()
, bukan menginstansiasinya kembali.
Informasi selengkapnya tersedia dalam e-book Panduan Utama untuk Memprofilkan game Unity.
Mengelola update kanvas UI:
- Perubahan dinamis pada elemen UI — Saat elemen UI seperti properti Teks, Gambar, atau
RectTransform
diperbarui (misalnya, mengubah konten teks, mengubah ukuran elemen, atau menganimasikan posisi), mesin dapat mengalokasikan memori untuk objek sementara. - Alokasi string — Elemen UI seperti Text sering memerlukan update string, karena string tidak dapat diubah dalam sebagian besar bahasa pemrograman.
- Kanvas kotor — Jika ada perubahan pada kanvas (misalnya, mengubah ukuran, mengaktifkan dan menonaktifkan elemen, atau mengubah properti tata letak), seluruh kanvas atau sebagiannya dapat ditandai sebagai kotor dan dibangun ulang. Hal ini dapat memicu pembuatan struktur data sementara (misalnya, data mesh, buffer vertex, atau perhitungan tata letak), yang menambah pembuatan sampah.
- Update yang kompleks atau sering — Jika kanvas memiliki banyak elemen atau sering diperbarui (misalnya, setiap frame), pembangunan ulang ini dapat menyebabkan churn memori yang signifikan.
- Perubahan dinamis pada elemen UI — Saat elemen UI seperti properti Teks, Gambar, atau
Aktifkan GC inkremental untuk mengurangi lonjakan pengumpulan besar dengan menyebarkan pembersihan alokasi ke beberapa frame. Lakukan profiling untuk memverifikasi apakah opsi ini meningkatkan performa dan footprint memori game Anda.
Jika game Anda memerlukan pendekatan yang terkontrol, tetapkan mode pengumpulan sampah ke manual. Kemudian, saat perubahan level atau di waktu lain tanpa gameplay aktif, panggil pengumpulan sampah.
Panggil panggilan pengumpulan sampah manual GC.Collect() untuk transisi status game (misalnya, pergantian level).
Mengoptimalkan array dimulai dari praktik kode sederhana dan, jika perlu, dengan menggunakan array native atau penampung native lainnya untuk array besar.
Pantau objek terkelola menggunakan alat seperti Unity Memory Profiler untuk melacak referensi objek tidak terkelola yang tetap ada setelah penghancuran.
Gunakan Penanda Profiler untuk dikirimkan ke Alat Pelaporan Performa untuk pendekatan otomatis.
Menghindari kebocoran dan fragmentasi memori
Kebocoran memori
Dalam kode C#, jika referensi ke Objek Unity ada setelah
objek dihancurkan, objek wrapper terkelola, yang dikenal sebagai Managed
Shell, tetap berada dalam memori. Memori native yang terkait dengan
referensi dilepaskan saat adegan dibongkar atau saat GameObject yang
memori terlampir padanya, atau salah satu objek induknya, dihancurkan melalui
metode Destroy()
. Namun, jika referensi lain ke Scene atau GameObject tidak dihapus, memori terkelola dapat tetap ada sebagai Objek Shell yang Bocor. Untuk mengetahui detail selengkapnya tentang Managed Shell Objects, lihat panduan Managed Shell Objects.
Selain itu, kebocoran memori dapat disebabkan oleh langganan peristiwa, lambda dan penutupan, penggabungan string, dan pengelolaan objek gabungan yang tidak tepat:
- Untuk memulai, lihat Menemukan kebocoran memori untuk membandingkan snapshot memori Unity dengan benar.
- Periksa langganan peristiwa dan kebocoran memori. Jika objek berlangganan ke peristiwa (misalnya, dengan delegasi atau UnityEvents), tetapi tidak berhenti berlangganan dengan benar sebelum dihancurkan, pengelola atau penerbit peristiwa dapat mempertahankan referensi ke objek tersebut. Hal ini mencegah objek tersebut dikumpulkan sampah memori, sehingga menyebabkan kebocoran memori.
- Pantau peristiwa class singleton atau global yang tidak dibatalkan pendaftarannya saat penghancuran objek. Misalnya, berhenti berlangganan atau melepaskan delegasi di destruktor objek.
- Pastikan penghancuran objek gabungan sepenuhnya membatalkan referensi ke komponen mesh teks, tekstur, dan GameObjects induk.
- Perlu diingat bahwa saat membandingkan snapshot Unity Memory Profiler dan mengamati perbedaan konsumsi memori tanpa alasan yang jelas, perbedaan tersebut mungkin disebabkan oleh driver grafis atau sistem operasi itu sendiri.
Fragmentasi memori
Fragmentasi memori terjadi saat banyak alokasi kecil dibebaskan dalam urutan acak. Alokasi heap dilakukan secara berurutan, yang berarti potongan memori baru dibuat saat potongan sebelumnya kehabisan ruang. Akibatnya, objek baru tidak mengisi area kosong pada chunk lama, sehingga menyebabkan fragmentasi. Selain itu, alokasi sementara yang besar dapat menyebabkan fragmentasi permanen selama durasi sesi game.
Masalah ini sangat bermasalah saat alokasi besar yang berumur pendek dibuat di dekat alokasi yang berumur panjang.
Kelompokkan alokasi berdasarkan masa pakainya; idealnya, alokasi yang berumur panjang harus dilakukan bersama-sama, di awal siklus proses aplikasi.
Pengamat dan pengelola acara
- Selain masalah yang disebutkan di bagian (Kebocoran Memori)77, seiring waktu, kebocoran memori dapat menyebabkan fragmentasi dengan membiarkan memori yang tidak digunakan dialokasikan ke objek yang tidak lagi digunakan.
- Pastikan penghancuran objek yang dikumpulkan sepenuhnya membatalkan referensi ke
komponen mesh teks, tekstur, dan
GameObjects
induk. - Pengelola acara sering membuat dan menyimpan daftar atau kamus untuk mengelola langganan acara. Jika objek ini bertambah dan berkurang secara dinamis selama runtime, objek ini dapat berkontribusi pada fragmentasi memori karena alokasi dan dealokasi yang sering.
Kode
- Coroutine terkadang mengalokasikan memori, yang dapat dengan mudah dihindari dengan menyimpan dalam cache pernyataan yang ditampilkan IEnumerator daripada mendeklarasikan yang baru setiap saat.
- Pantau terus status siklus proses objek yang dikelompokkan untuk menghindari penyimpanan
referensi hantu
UnityEngine.Object
.
Aset
- Gunakan sistem penggantian dinamis untuk pengalaman game berbasis teks guna menghindari pemuatan awal semua font untuk kasus multi-bahasa.
- Atur aset (misalnya, tekstur dan partikel) bersama-sama menurut jenis dan siklus proses yang diharapkan.
- Memadatkan aset dengan atribut siklus proses tidak aktif, seperti gambar UI yang berlebihan dan mesh statis.
Alokasi berbasis masa aktif
- Alokasikan aset yang berumur panjang di awal siklus proses aplikasi untuk memastikan alokasi yang ringkas.
- Gunakan NativeCollections atau alokator kustom untuk struktur data yang intensif memori atau sementara (misalnya, cluster fisika).
Tindakan memori terkait kode dan file yang dapat dieksekusi
File yang dapat dieksekusi dan plugin game juga memengaruhi penggunaan memori.
Metadata IL2CPP
IL2CPP membuat metadata untuk setiap jenis (misalnya, class, generik, dan delegasi) pada waktu build, yang kemudian digunakan pada waktu runtime untuk refleksi, pemeriksaan jenis, dan operasi khusus runtime lainnya. Metadata ini disimpan dalam memori dan dapat berkontribusi secara signifikan terhadap total jejak memori aplikasi. Cache metadata IL2CPP memberikan kontribusi yang signifikan terhadap waktu inisialisasi dan pemuatan. Selain itu, IL2CPP tidak menghapus duplikat elemen metadata tertentu (misalnya, jenis generik atau informasi yang diserialkan), yang dapat menyebabkan penggunaan memori yang berlebihan. Hal ini diperparah dengan penggunaan huruf yang berulang atau berlebihan dalam project.
Metadata IL2CPP dapat dikurangi dengan:
- Hindari penggunaan reflection API, karena dapat menjadi kontributor signifikan untuk alokasi metadata IL2CPP
- Menonaktifkan paket bawaan
- Menerapkan berbagi generik penuh Unity 2022, yang akan membantu mengurangi overhead yang disebabkan oleh generik. Namun, untuk membantu mengurangi alokasi lebih lanjut, kurangi penggunaan generik.
Penghapusan kode
Selain mengurangi ukuran build, penghapusan kode juga mengurangi penggunaan memori. Saat membangun terhadap backend pembuatan skrip IL2CPP, penghapusan bytecode terkelola (yang diaktifkan secara default) akan menghapus kode yang tidak digunakan dari assembly terkelola. Proses ini berfungsi dengan menentukan assembly root, lalu menggunakan analisis kode statis untuk menentukan kode terkelola lain yang digunakan oleh assembly root tersebut. Kode yang tidak dapat dijangkau akan dihapus. Untuk mengetahui informasi selengkapnya tentang Penghapusan Kode Terkelola, lihat postingan blog TTales from the optimization trenches: Better managed code stripping with Unity 2020 LTS dan dokumentasi Managed code stripping.
Pengalokasi native
Bereksperimen dengan pengalokasi memori native untuk menyempurnakan pengalokasi memori. Jika game memiliki memori rendah, gunakan blok memori yang lebih kecil, meskipun ini melibatkan alokator yang lebih lambat. Lihat contoh alokator heap dinamis untuk mempelajari lebih lanjut.
Mengelola plugin dan SDK native
Temukan plugin yang bermasalah — Hapus setiap plugin dan bandingkan snapshot memori game. Hal ini melibatkan penonaktifan banyak fungsi kode dengan Scripting Define Symbols dan memfaktorkan ulang class yang sangat terkait dengan antarmuka. Lihat Tingkatkan kualitas kode Anda dengan pola pemrograman game untuk memfasilitasi proses menonaktifkan dependensi eksternal tanpa membuat game Anda tidak dapat dimainkan.
Hubungi penulis plugin atau SDK — Sebagian besar plugin bukan open source.
Mereproduksi penggunaan memori plugin — Anda dapat menulis plugin sederhana (gunakan plugin Unity ini sebagai referensi) yang melakukan alokasi memori. Periksa snapshot memori menggunakan Android Studio (karena Unity tidak melacak alokasi ini) atau panggil class
MemoryInfo
dan metodeRuntime.totalMemory()
dalam project yang sama.
Plugin Unity mengalokasikan memori Java dan native; berikut cara melakukannya:
Java
byte[] largeObject = new byte[1024 * 1024 * megaBytes];
list.add(largeObject);
Native
char* buffer = new char[megabytes * 1024 * 1024];
// Random data to fill the buffer
for (int i = 1; i < megabytes * 1024 * 1024; ++i) {
buffer[i] = 'A' + (i % 26); // Fill with letters A-Z
}