Halo, Habr! Pada artikel ini saya akan mencoba memberi tahu manajemen memori apa dalam program / aplikasi dari sudut pandang programmer aplikasi. Ini bukan panduan atau manual lengkap, tetapi hanya gambaran umum dari masalah yang ada dan beberapa pendekatan untuk menyelesaikannya.
Mengapa ini perlu? Program adalah urutan instruksi pemrosesan data (dalam kasus yang paling umum). Data ini perlu disimpan , dimuat , ditransfer , dll. Dalam beberapa cara. Semua operasi ini tidak terjadi secara instan, oleh karena itu, mereka secara langsung mempengaruhi kecepatan aplikasi akhir Anda. Kemampuan untuk mengelola data secara optimal dalam proses kerja akan memungkinkan Anda untuk membuat program yang sangat tidak sepele dan sangat menuntut sumber daya.
Catatan: sebagian besar materi disajikan dengan contoh-contoh dari gim / mesin gim (karena topik ini lebih menarik bagi saya secara pribadi), namun, sebagian besar materi dapat diterapkan ke server penulisan, aplikasi pengguna, paket grafik, dll.

Tidak mungkin untuk mengingat semuanya. Tetapi jika Anda tidak berhasil memuatnya, Anda akan mendapatkan sabun
Langsung dari kelelawar
Itu terjadi di industri bahwa proyek game AAA besar dikembangkan terutama pada mesin yang ditulis menggunakan C ++. Salah satu fitur dari bahasa ini adalah perlunya manajemen memori manual. Java / C # dll. Mereka membanggakan pengumpulan sampah (GarbageCollection / GC) - kemampuan untuk membuat objek dan masih tidak membebaskan memori yang digunakan dengan tangan. Proses ini menyederhanakan dan mempercepat pengembangan, tetapi juga dapat menyebabkan beberapa masalah: seorang pemulung sampah yang dipicu secara berkala dapat membunuh semua waktu lunak-nyata dan menambahkan pembekuan yang tidak menyenangkan ke dalam permainan.
Ya, dalam proyek-proyek seperti "Minecraft" GC mungkin tidak terlihat, seperti mereka umumnya tidak menuntut pada sumber daya komputer, tetapi permainan seperti "Red Dead Redemption 2", "God of War", "Last of Us" bekerja "hampir" pada puncak kinerja sistem dan karenanya tidak hanya perlu besar jumlah sumber daya, tetapi juga dalam distribusi yang kompeten.
Selain itu, bekerja di lingkungan dengan alokasi memori otomatis dan pengumpulan sampah, Anda mungkin menghadapi kurangnya fleksibilitas dalam pengelolaan sumber daya. Bukan rahasia lagi bahwa Java menyembunyikan semua detail implementasi dan aspek pekerjaannya di bawah tenda, sehingga pada output Anda hanya memiliki antarmuka yang diinstal untuk berinteraksi dengan sumber daya sistem, tetapi mungkin tidak cukup untuk menyelesaikan beberapa masalah. Misalnya, memulai algoritme dengan jumlah alokasi memori yang tidak konstan di setiap frame (ini bisa berupa pencarian jalur AI, memeriksa visibilitas, animasi, dll.) Mengarah pada penurunan kinerja yang sangat buruk.
Bagaimana alokasi dalam kode terlihat
Sebelum melanjutkan diskusi, saya ingin menunjukkan bagaimana kerja dengan memori dalam C / C ++ secara langsung terjadi dengan beberapa contoh. Secara umum, antarmuka standar dan paling sederhana untuk mengalokasikan memori proses diwakili oleh operasi berikut:
// size void* malloc(size_t size); // p void free(void* p);
Di sini Anda dapat menambahkan tentang fungsi tambahan yang memungkinkan Anda mengalokasikan memori yang selaras:
// C11 - , * alignment void* aligned_alloc(size_t size, size_t alignment); // Posix - // address (*address = allocated_mem_p) int posix_memalign(void** address, size_t alignment, size_t size);
Harap dicatat bahwa platform yang berbeda dapat mendukung standar fungsi yang berbeda, tersedia misalnya pada MacOS dan tidak tersedia saat menang.
Ke depan, area memori yang selaras secara khusus mungkin diperlukan bagi Anda berdua untuk mencapai jalur cache prosesor dan untuk perhitungan menggunakan set register yang diperluas ( SSE , MMX , AVX , dll.).
Contoh program mainan yang mengalokasikan memori dan mencetak nilai buffer, menafsirkannya sebagai bilangan bulat yang ditandatangani:
/* main.cpp */ #include <cstdio> #include <cstdlib> int main(int argc, char** argv) { const int N = 10; int* buffer = (int*) malloc(sizeof(int) * N); for(int i = 0; i < N; i++) { printf("%i ", buffer[i]); } free(buffer); return 0; }
Pada macOS 10.14, program ini dapat dibangun dan dijalankan dengan serangkaian perintah berikut:
$ clang++ main.cpp -o main $ ./main
Catatan: selanjutnya saya tidak ingin menutup operasi C ++ seperti baru / hapus, karena mereka lebih cenderung digunakan untuk membangun / menghancurkan objek secara langsung, tetapi mereka menggunakan operasi yang biasa dengan memori seperti malloc / free.
Masalah memori
Ada beberapa masalah yang muncul saat bekerja dengan RAM komputer. Semua dari mereka, satu atau lain cara, disebabkan tidak hanya oleh fitur-fitur OS dan perangkat lunak, tetapi juga oleh arsitektur besi di mana semua hal ini bekerja.
1. Jumlah memori
Sayangnya, memori secara fisik terbatas. Di PlayStation 4, ini adalah 8 GiB GDDR5, 3,5 GiB yang cadangan sistem operasinya untuk kebutuhannya . Memori virtual dan pertukaran halaman tidak akan banyak membantu, karena menukar halaman ke disk adalah operasi yang sangat lambat (dalam frame N tetap per detik, jika kita berbicara tentang permainan).
Perlu juga dicatat " anggaran " terbatas - beberapa batasan artifisial pada jumlah memori yang digunakan, dibuat untuk menjalankan aplikasi pada beberapa platform. Jika Anda membuat game untuk platform seluler dan ingin mendukung tidak hanya satu, tetapi seluruh lini perangkat, Anda harus membatasi selera Anda demi menyediakan pasar penjualan yang lebih luas. Ini dapat dicapai baik dengan hanya membatasi konsumsi RAM, dan oleh kemampuan untuk mengkonfigurasi pembatasan ini tergantung pada gadget di mana permainan sebenarnya dimulai.
2. Fragmentasi
Efek tidak menyenangkan yang muncul selama proses alokasi beberapa bagian memori dengan berbagai ukuran. Akibatnya, Anda mendapatkan ruang alamat yang terpecah menjadi banyak bagian yang terpisah. Menggabungkan bagian-bagian ini menjadi satu blok dengan ukuran yang lebih besar tidak akan berfungsi, karena bagian dari memori ditempati, dan kita tidak dapat memindahkannya dengan bebas.

Fragmentasi dengan contoh alokasi berurutan dan rilis blok memori
Sebagai hasilnya: kita dapat memiliki memori bebas yang cukup secara kuantitatif, tetapi tidak secara kualitatif. Dan permintaan berikutnya, katakanlah, "mengalokasikan ruang untuk trek audio", pengalokasi tidak akan dapat memuaskan, karena tidak ada satu pun memori ukuran ini.
3. cache CPU

Hirarki memori komputer
Cache prosesor modern adalah tautan perantara yang menghubungkan memori utama (RAM) dan prosesor mendaftar secara langsung. Kebetulan akses baca / tulis ke memori adalah operasi yang sangat lambat (jika kita berbicara tentang jumlah siklus jam CPU yang diperlukan untuk mengeksekusi). Oleh karena itu, ada beberapa hierarki cache (L1, L2, L3, dll.), Yang memungkinkan, "menurut beberapa prediksi" untuk memuat data dari RAM, atau secara perlahan mendorongnya ke dalam memori yang lebih lambat.
Menempatkan objek dengan tipe yang sama dalam satu baris dalam memori memungkinkan Anda untuk "secara signifikan" mempercepat proses memprosesnya (jika pemrosesan terjadi secara berurutan), karena dalam hal ini lebih mudah untuk memprediksi data apa yang akan dibutuhkan selanjutnya. Dan dengan "signifikan" berarti peningkatan produktivitas di kali. Pengembang mesin Unity telah berulang kali membicarakan hal ini dalam laporan mereka di GDC .
4. Multi-threading
Memastikan akses aman ke memori bersama di lingkungan multi-utas adalah salah satu masalah utama yang harus Anda selesaikan saat membuat mesin gim Anda sendiri / gim / aplikasi lain yang menggunakan banyak utas untuk mencapai kinerja yang lebih besar. Komputer modern diatur dengan cara yang sangat tidak sepele. Kami memiliki struktur cache yang kompleks dan beberapa inti kalkulator. Semua ini, jika digunakan secara tidak benar, dapat menyebabkan situasi ketika data yang dibagikan dari proses Anda akan rusak sebagai akibat dari beberapa utas (jika mereka secara bersamaan mencoba untuk bekerja dengan data ini tanpa kontrol akses). Dalam kasus paling sederhana, akan terlihat seperti ini:

Saya tidak ingin mempelajari topik pemrograman multi-utas, karena banyak aspeknya jauh melampaui lingkup artikel atau bahkan keseluruhan buku.
5. Malloc / gratis
Operasi alokasi / rilis tidak terjadi secara instan. Pada sistem operasi modern, jika kita berbicara tentang Windows / Linux / MacOS, mereka diimplementasikan dengan baik dan bekerja dengan cepat di sebagian besar situasi . Tetapi berpotensi ini adalah operasi yang sangat memakan waktu. Tidak hanya ini panggilan sistem, tetapi tergantung pada implementasinya, mungkin perlu beberapa saat untuk menemukan bagian memori yang sesuai (First Fit, Best fit, dll.) Atau untuk menemukan tempat untuk memasukkan dan / atau menggabungkan area yang dibebaskan.
Selain itu, memori yang baru dialokasikan mungkin tidak benar-benar dipetakan ke halaman fisik nyata, yang mungkin juga memakan waktu pada akses pertama.
Ini adalah detail implementasi, tetapi bagaimana dengan penerapannya? Malloc / baru tidak tahu di mana, bagaimana, atau mengapa Anda memanggil mereka. Mereka mengalokasikan memori (dalam kasus terburuk) dari 1 KiB dan 100 MiB sama-sama ... sama buruknya. Secara langsung, strategi penggunaan diserahkan kepada programmer atau orang yang mengimplementasikan runtime program Anda.
6. Memori rusak
Seperti kata wiki , ini adalah salah satu kesalahan yang paling tidak terduga yang hanya muncul selama program, dan paling sering disebabkan langsung oleh kesalahan dalam penulisan program ini. Tapi apa masalahnya? Untungnya (atau sayangnya), ini tidak terkait dengan kerusakan komputer Anda. Sebaliknya, ini menampilkan situasi di mana Anda mencoba untuk bekerja dengan memori yang bukan milik Anda . Saya akan jelaskan sekarang:
- Ini mungkin merupakan upaya untuk membaca / menulis ke sebagian memori yang tidak terisi.
- Melampaui batas blok memori yang disediakan untuk Anda. Masalah ini adalah semacam kasus khusus masalah (1), tetapi lebih buruk karena sistem akan memberi tahu Anda bahwa Anda melampaui batas hanya ketika Anda membiarkan halaman ditampilkan untuk Anda. Artinya, berpotensi, masalah ini sangat sulit ditangkap, karena OS hanya dapat merespons jika Anda meninggalkan batas halaman virtual yang ditampilkan kepada Anda. Anda dapat merusak memori proses dan mendapatkan kesalahan yang sangat aneh dari tempat yang tidak diharapkan sama sekali.
- Melepaskan memori yang sudah dibebaskan (terdengar aneh) atau belum dialokasikan
- dll.
Dalam C / C ++, di mana ada aritmatika pointer, Anda akan menemukan ini satu atau dua kali. Namun, di Java Runtime, Anda harus berkeringat cukup keras untuk mendapatkan kesalahan semacam ini (saya belum mencobanya sendiri, tapi saya pikir ini mungkin, kalau tidak hidup akan terlalu sederhana).
7. Kebocoran memori
Ini adalah kasus khusus dari masalah yang lebih umum yang terjadi dalam banyak bahasa pemrograman. Pustaka C / C ++ standar menyediakan akses ke sumber daya OS. Itu bisa berupa file, soket, memori, dll. Setelah digunakan, sumber daya harus ditutup dengan benar dan
memori yang ditempati olehnya harus dibebaskan. Dan jika kita berbicara secara khusus tentang membebaskan kebocoran yang diakumulasikan sebagai akibat dari program dapat menyebabkan kesalahan "kehabisan memori" ketika OS tidak akan dapat memenuhi permintaan alokasi berikutnya. Seringkali, pengembang hanya lupa untuk membebaskan memori yang digunakan karena satu dan lain alasan.
Di sini perlu ditambahkan tentang penutupan dan pelepasan sumber daya yang benar pada GPU, karena driver awal tidak memungkinkan untuk melanjutkan bekerja dengan kartu video jika sesi sebelumnya tidak selesai dengan benar. Hanya me-reboot sistem yang bisa menyelesaikan masalah ini, yang sangat meragukan - untuk memaksa pengguna me-reboot sistem setelah menjalankan aplikasi Anda.
8. Menggantung pointer
Pointer menggantung adalah beberapa jargon yang menggambarkan situasi di mana pointer merujuk ke nilai yang tidak valid. Situasi serupa dapat dengan mudah muncul ketika menggunakan pointer C-style klasik dalam program C / C ++. Misalkan Anda mengalokasikan memori, menyimpan alamatnya ke dalam pointer p, dan kemudian membebaskan memori (lihat contoh kode):
// void* p = malloc(size); // ... - // free(p); // p? // *p == ?
Pointer menyimpan beberapa nilai, yang dapat kita artikan sebagai alamat blok memori. Kebetulan kami tidak dapat mengatakan apakah blok memori ini valid atau tidak. Hanya seorang programmer, berdasarkan perjanjian tertentu, dapat beroperasi dengan pointer. Dimulai dengan C ++ 11, sejumlah pointer "pointer pintar" tambahan diperkenalkan ke perpustakaan standar, yang memungkinkan untuk melemahkan kontrol sumber daya oleh programmer dengan menggunakan meta-informasi tambahan di dalam diri mereka (lebih lanjut tentang ini nanti).
Sebagai solusi parsial, Anda dapat menggunakan nilai khusus dari pointer, yang akan memberi sinyal kepada kami bahwa tidak ada apa-apa di alamat ini. Di C, makro NULL digunakan sebagai nilai dari nilai ini, dan di C ++, kata kunci bahasa nullptr digunakan. Solusinya parsial, karena:
- Nilai pointer harus ditetapkan secara manual, sehingga programmer bisa lupa melakukannya.
- nullptr atau hanya 0x0 termasuk dalam set nilai yang diterima oleh pointer, yang tidak baik ketika keadaan khusus suatu objek diekspresikan melalui keadaan biasa. Ini adalah semacam warisan, dan berdasarkan kesepakatan, OS tidak akan mengalokasikan kepada Anda sepotong memori yang alamatnya dimulai dengan 0x0.
Kode sampel dengan nol:
// - p free(p); p = nullptr; // p == nullptr ,
Anda dapat mengotomatiskan proses ini sampai batas tertentu:
void _free(void* &p) { free(p); p = nullptr; } // - p _free(p); // p == nullptr, //
9. Jenis memori
RAM adalah memori akses acak umum untuk keperluan umum, akses yang melalui bus pusat memiliki semua inti prosesor dan perangkat periferal Anda. Volumenya bervariasi, tetapi paling sering kita berbicara tentang N gigabytes, di mana N adalah 1,2,4,8,16 dan seterusnya. Panggilan malloc / free berusaha untuk menempatkan blok memori yang Anda inginkan tepat di RAM komputer.
VRAM (memori video) - memori video, yang disertakan dengan kartu video / akselerator video PC Anda. Sebagai aturan, lebih kecil dari RAM (sekitar 1,2,4 GiB), tetapi memiliki kecepatan tinggi. Distribusi jenis memori ini ditangani oleh driver kartu video, dan paling sering Anda tidak memiliki akses langsung ke sana.
Tidak ada pemisahan seperti itu pada PlayStation 4, dan semua RAM diwakili oleh 8 gigabytes tunggal pada GDDR5. Karena itu, semua data untuk prosesor dan akselerator video ada di dekatnya.
Manajemen sumber daya yang baik di mesin permainan mencakup alokasi memori yang kompeten baik di RAM Utama maupun di sisi VRAM. Di sini Anda mungkin mengalami duplikasi ketika data yang sama ada di sana-sini, atau dengan transfer data yang berlebihan dari RAM ke VRAM dan sebaliknya.
Sebagai ilustrasi untuk semua masalah yang disuarakan : Anda dapat melihat aspek-aspek komputer perangkat pada contoh arsitektur PlayStation 4 (Gbr.). Berikut adalah prosesor pusat, 8 core, cache level L1 dan L2, bus data, RAM, akselerator grafis, dll. Untuk deskripsi lengkap dan terperinci, lihat "Arsitektur Mesin Game" karya Greg Gregory.

Arsitektur PlayStation 4
Pendekatan Umum
Tidak ada solusi universal. Tetapi ada satu set beberapa poin di mana Anda harus fokus jika Anda akan menerapkan alokasi dan manajemen memori secara manual dalam aplikasi Anda. Ini termasuk kontainer dan pengalokasi khusus, strategi alokasi memori, desain sistem / game, manajer sumber daya, dan banyak lagi.
Jenis pengalokasi
Penggunaan pengalokasi memori khusus didasarkan pada ide berikut: Anda tahu ukuran apa, pada saat apa pekerjaan dan di tempat apa Anda akan membutuhkan potongan memori. Oleh karena itu, Anda dapat mengalokasikan memori yang diperlukan, entah bagaimana menyusunnya dan menggunakan / menggunakannya kembali. Ini adalah ide umum / konsep menggunakan pengalokasi khusus. Apa mereka (tentu saja, tidak semua) dapat dilihat lebih lanjut:
Pengalokasi linier
Merupakan penyangga ruang alamat yang berdekatan. Dalam perjalanan kerja, ini memungkinkan Anda untuk mengalokasikan bagian memori dengan ukuran sewenang-wenang (sedemikian rupa sehingga sesuai dengan buffer). Tetapi Anda dapat membebaskan semua memori yang dialokasikan hanya 1 kali. Artinya, sepotong memori sewenang-wenang tidak dapat dibebaskan - itu akan tetap seolah-olah ditempati sampai seluruh buffer ditandai sebagai bersih. Desain ini menyediakan alokasi dan pelepasan O (1), yang memberikan jaminan kecepatan dalam kondisi apa pun.

Kasus penggunaan umum: dalam proses memperbarui keadaan proses (setiap frame dalam game) Anda dapat menggunakan LinearAllocator untuk mengalokasikan buffer tmp untuk kebutuhan teknis: pemrosesan input, bekerja dengan string, parsing perintah ConsoleManager dalam mode debug, dll.
Alokasi pengalokasian
Modifikasi pengalokasi linier. Memungkinkan Anda mengosongkan memori dengan urutan alokasi terbalik, dengan kata lain, berperilaku seperti tumpukan biasa menurut prinsip LIFO. Ini bisa sangat berguna untuk melakukan perhitungan matematis yang dimuat (hierarki transformasi), untuk mengimplementasikan pekerjaan subsistem scripting, untuk setiap perhitungan di mana prosedur yang ditunjukkan untuk membebaskan memori diketahui sebelumnya.

Kesederhanaan desain memberikan alokasi memori O (1) dan kecepatan membebaskan.
Pengalokasi kolam renang
Memungkinkan Anda mengalokasikan blok memori dengan ukuran yang sama. Ini dapat diimplementasikan sebagai penyangga ruang alamat kontinu, dibagi menjadi blok-blok dengan ukuran yang telah ditentukan. Blok ini dapat membentuk daftar tertaut. Dan kita selalu tahu blok mana yang akan diberikan dalam alokasi berikutnya. Informasi meta ini dapat disimpan di blok itu sendiri, yang memaksakan pembatasan pada ukuran blok minimum (sizeof (void *)). Pada kenyataannya, ini tidak kritis.

Karena semua blok memiliki ukuran yang sama, tidak masalah bagi kami untuk mengembalikan blok mana, dan oleh karena itu, semua operasi alokasi / deallokasi dapat dilakukan dalam O (1).
Pengalokasi bingkai
Alokasi linear tetapi hanya dengan referensi ke frame saat ini - memungkinkan Anda untuk melakukan alokasi memori tmp dan kemudian secara otomatis melepaskan semuanya ketika mengubah frame. Ini harus dipilih secara terpisah, karena ini adalah entitas global dan unik dalam kerangka permainan runtime, dan oleh karena itu dapat dibuat dari ukuran yang sangat mengesankan, katakanlah beberapa lusin MiB, yang akan sangat berguna ketika memuat sumber daya dan memprosesnya.
Pengalokasi bingkai ganda
Ini adalah pengalokasi bingkai ganda, tetapi dengan beberapa fitur. Ini memungkinkan Anda untuk mengalokasikan memori dalam bingkai saat ini, dan menggunakannya dalam frame saat ini dan berikutnya. Yaitu, memori yang Anda alokasikan dalam bingkai N akan dibebaskan hanya setelah bingkai N +1. Ini diwujudkan dengan beralih frame aktif untuk menyorot di akhir setiap frame.

Tapi jenis pengalokasi ini, seperti yang sebelumnya, memberlakukan sejumlah pembatasan pada masa objek yang dibuat dalam memori yang dialokasikan untuk itu. Oleh karena itu, Anda harus menyadari bahwa pada akhir frame, data menjadi tidak valid, dan akses berulang ke mereka dapat menyebabkan masalah serius.
Pengalokasi statis
Jenis pengalokasi ini mengalokasikan memori dari buffer yang diperoleh, misalnya, pada tahap peluncuran program, atau ditangkap pada tumpukan dalam bingkai fungsi. Berdasarkan jenisnya, ini dapat benar-benar pengalokasi apa pun: linier, kumpulan, tumpukan. Mengapa disebut statis ? Ukuran buffer memori yang ditangkap harus diketahui pada tahap kompilasi program. Ini memberlakukan batasan yang signifikan: jumlah memori yang tersedia untuk pengalokasi ini tidak dapat diubah selama operasi. Tapi apa manfaatnya? Buffer yang digunakan akan secara otomatis ditangkap dan kemudian dibebaskan (baik setelah selesai bekerja atau saat keluar dari fungsi). Ini tidak memuat tumpukan, menyelamatkan Anda dari fragmentasi, memungkinkan Anda untuk dengan cepat mengalokasikan memori pada tempatnya.
Anda dapat melihat contoh kode menggunakan pengalokasi ini, jika Anda perlu memecah string menjadi substring dan melakukan sesuatu dengannya:

Dapat juga dicatat bahwa penggunaan memori dari stack secara teori jauh lebih efisien, karena susun bingkai fungsi saat ini dengan probabilitas tinggi sudah akan ada di cache prosesor.
Semua pengalokasi ini entah bagaimana menyelesaikan masalah dengan fragmentasi, dengan kurangnya memori, dengan kecepatan menerima dan melepaskan blok ukuran yang diperlukan, dengan umur objek dan memori yang mereka tempati.
Juga harus dicatat bahwa pendekatan yang tepat untuk desain antarmuka akan memungkinkan Anda untuk membuat semacam hierarki pengalokasi ketika, misalnya: kumpulan mengalokasikan memori dari alokasi bingkai, dan alokasi bingkai pada gilirannya mengalokasikan memori dari alokasi linier. Struktur serupa dapat dilanjutkan lebih lanjut, beradaptasi dengan tugas dan kebutuhan Anda.

Saya melihat antarmuka serupa untuk membuat hierarki sebagai berikut:
class IAllocator { public: virtual void* alloc(size_t size) = 0; virtual void* alloc(size_t size, size_t alignment) = 0; virtual void free (void* &p) = 0; }
malloc/free , . , , . / , .
Smart pointer — C++ ++11 ( boost, ). -, , - , . .
? :
- (/)
:
Unique pointer
1 ( ).
unique pointer , . , .. 1 / .
uniquePtr1 uniquePtr2, uniquePtr1 , . 1 .

Shared pointer
(reference counting). , , . , , , .

. -, , . . -, - .
Weak pointer
. , . Apa artinya ini? shared pointer. , shared pointer , . , shared pointer weak pointer. , (shared) , weak pointer shared pointer. — weak pointer , , , .

shared, weak pointer meta-data . - , .. , O(N) overhead , N — - . , . , . .
: . , shared pointer, , ( ) - - - . . meta-info , , . Contoh:
/* */ /* , shared pointer */ Array<TSharedPtr<Object>> objects; objects.add(newShared<Object>(...)); ... objects.add(newShared<Object>(...));
/* ( meta-info ) */ Array<Object> objects; objects.emplace(...); ... objects.emplace(...);
. . Tentang itu lebih jauh.
Unique id
, . (id/identificator), , , -. :
, id. , , , id.
, ( , )
id , , id.
. , id, .
: id, , id, .
id , (Vulkan, OpenGL), (Godot, CryEngine). EntityID CryEngine .
, id : . , ( ), , .
/* */ class ID { uint32 index; uint32 generation; }
/* - / */ class ObjectManager { public: ID create(...); void destroy(ID); void update(ID id, ...); private: Array<uint32> generations; Array<Objects> objects; }
ID , ID . :
generation = generations[id.index]; if (generation == id.generation) then /* */ else /* , */
id generation 1 id ids.
C++ , . std, , . :
- Linked list —
- Array — /
- Queue —
- Stack —
- Map —
- Set —
? memory corruption. / , , , , .
, , . , , / .
, , . , ( ) . , malloc/free , , .
? , (/ ), , , . , , , .

ryEngine Sandbox:
, Unreal, Unity, CryEngine ., , . , , , — , .
Pre-allocating
, / .
: malloc/free . , "run out of memory", . . , (, , .).
. . , - . , malloc/free, : , , .
. : , , , .. .
: , , , . open-source , , . , , — malloc/free.
GDC CD Project Red , , "The Witcher: Blood and Wine" () . , , , , .

Naughty Dog , "Uncharted 4: A Thief's End" , (, ) .

Kesimpulan
, , , . , . / , , - .. , (, ).