Bagaimana Anda bersiap untuk kondisi di luar memori?

18

Ini bisa mudah untuk game dengan cakupan yang ditentukan dengan baik, tetapi pertanyaannya adalah tentang game sandbox, di mana pemain diizinkan untuk membuat dan membangun apa pun .

Kemungkinan teknik:

  • Gunakan kumpulan memori dengan batas atas.
  • Hapus objek yang tidak lagi dibutuhkan secara berkala.
  • Alokasikan jumlah memori tambahan di awal sehingga dapat dibebaskan nanti sebagai mekanisme pemulihan. Saya akan mengatakan sekitar 2-4 MB.

Ini lebih cenderung terjadi pada platform seluler / konsol di mana memori biasanya terbatas tidak seperti PC 16 GB Anda. Saya berasumsi Anda memiliki kontrol penuh atas alokasi / deallokasi memori dan tidak ada pengumpulan sampah yang terlibat. Itu sebabnya saya menandai ini sebagai C ++.

Harap dicatat bahwa saya tidak berbicara tentang C ++ Efektif Item 7 "Bersiaplah untuk kondisi kehabisan memori" , meskipun itu relevan, saya ingin melihat jawaban yang lebih terkait dengan pengembangan game, di mana Anda biasanya memiliki kontrol lebih besar atas apa kejadian.

Untuk meringkas pertanyaan, bagaimana Anda mempersiapkan kondisi kehabisan memori untuk game sandbox, ketika Anda menargetkan platform dengan konsol / ponsel memori terbatas?

concept3d
sumber
Alokasi memori yang gagal sangat jarang pada sistem operasi PC modern, karena mereka akan secara otomatis bertukar ke hard drive ketika kehabisan RAM fisik. Masih situasi yang harus dihindari, karena bertukar jauh lebih lambat daripada RAM fisik dan akan sangat mempengaruhi kinerja.
Philipp
@ Pilip ya saya tahu. Tapi pertanyaan saya lebih ke perangkat terbatas memori seperti konsol dan ponsel saya pikir saya sebutkan itu.
concept3d
Ini adalah pertanyaan yang cukup luas (dan semacam jajak pendapat seperti yang dikatakan). Bisakah Anda mempersempit ruang lingkup sedikit lebih spesifik untuk satu situasi?
MichaelHouse
@ Byte56 Saya mengedit pertanyaan. Saya harap ini memiliki ruang lingkup yang lebih jelas sekarang.
concept3d

Jawaban:

16

Secara umum, Anda tidak menangani kehabisan memori. Satu-satunya pilihan yang waras dalam perangkat lunak sebesar dan serumit gim adalah dengan hanya menabrak / menegaskan / mengakhiri pengalokasi memori Anda sesegera mungkin (terutama dalam pembuatan debug). Kondisi kehabisan memori diuji dan ditangani dalam beberapa perangkat lunak sistem inti atau perangkat lunak server dalam beberapa kasus tetapi tidak biasanya di tempat lain.

Ketika Anda memiliki kap memori atas, Anda hanya memastikan bahwa Anda tidak pernah membutuhkan lebih dari jumlah memori itu. Misalnya, Anda dapat menyimpan jumlah maksimum NPC yang diizinkan pada suatu waktu, dan cukup berhenti memunculkan NPC yang tidak penting baru begitu batasan itu dipukul. Untuk NPC esensial Anda dapat meminta mereka mengganti yang tidak penting atau memiliki kumpulan / batas terpisah untuk NPC esensial yang desainer Anda ketahui untuk mendesain (mis. Jika Anda hanya dapat memiliki 3 NPC esensial, desainer tidak akan menempatkan lebih dari 3 dalam area / chunk - alat yang baik akan membantu desainer melakukan ini dengan benar dan tentu saja pengujian sangat penting).

Sistem streaming yang sangat bagus juga penting terutama untuk gim sandbox. Anda tidak perlu menyimpan semua NPC dan item dalam memori. Saat Anda bergerak melalui potongan dunia, potongan baru akan mengalir dan potongan lama mengalir keluar. Ini umumnya akan mencakup NPC dan item serta medan. Desain dan batas teknis pada batas item harus ditetapkan dengan sistem ini dalam pikiran, mengetahui bahwa paling banyak potongan X lama akan disimpan dan potongan pro-aktif Y potongan baru akan dimuat, sehingga permainan perlu memiliki ruang untuk menyimpan semua data potongan X + Y + 1 dalam memori.

Beberapa gim berusaha menangani situasi kehabisan memori dengan pendekatan dua lintasan. Ingatlah bahwa sebagian besar gim memiliki banyak data yang secara teknis tidak perlu di-cache (katakanlah, potongan lama yang disebutkan di atas) dan alokasi memori mungkin melakukan sesuatu seperti:

allocate(bytes):
  if can_allocate(bytes):
    return internal_allocate(bytes)
  else:
    warning(LOW_MEMORY)
    tell_systems_to_dump_caches()

    if can_allocate(bytes):
      return internal_allocate(bytes)
    else:
      fatal_error(OUT_OF_MEMORY)

Ini adalah langkah terakhir untuk menangani situasi tak terduga dalam rilis tetapi selama debugging dan pengujian Anda mungkin harus segera crash. Anda tidak ingin harus bergantung pada hal-hal semacam ini (terutama karena membuang cache mungkin memiliki beberapa konsekuensi kinerja yang serius).

Anda mungkin juga mempertimbangkan untuk membuang salinan beresolusi tinggi dari beberapa data, misalnya Anda mungkin membuang tingkat tekstur mipmap beresolusi tinggi jika Anda kehabisan memori GPU (atau memori apa pun dalam arsitektur memori bersama). Ini biasanya membutuhkan banyak pekerjaan arsitektur untuk membuatnya sepadan.

Perhatikan bahwa beberapa gim sandbox yang sangat tidak terbatas dapat dengan mudah hanya macet, bahkan pada PC (ingat bahwa aplikasi 32-bit yang umum memiliki batas ruang alamat 2-3GB bahkan jika Anda memiliki PC dengan 128GB RAM; 64- bit OS dan perangkat keras memungkinkan lebih banyak aplikasi 32-bit berjalan secara bersamaan tetapi tidak dapat melakukan apa pun untuk membuat biner 32-bit memiliki ruang alamat yang lebih besar). Pada akhirnya, Anda memiliki dunia gim yang sangat fleksibel yang membutuhkan ruang memori tanpa batas untuk dijalankan dalam setiap kasus atau Anda memiliki dunia yang sangat terbatas dan terkontrol yang selalu bekerja dengan sempurna dalam memori yang terbatas (atau sesuatu di suatu tempat di antaranya).

Sean Middleditch
sumber
+1 untuk jawaban ini. Saya menulis dua sistem yang bekerja menggunakan gaya Sean dan kolam memori diskrit dan mereka berdua bekerja dengan baik dalam produksi. Pertama adalah spawner yang memutar kembali output pada kurva ke batas maksimal pemutusan sehingga pemain tidak akan pernah melihat pengurangan mendadak (pikir total throughput diturunkan oleh margin keselamatan itu). Kedua terkait dengan bongkahan karena alokasi yang gagal akan memaksa pembersihan dan realokasi. Saya merasa bahwa ** dunia yang sangat terbatas dan terkontrol yang selalu bekerja dengan sempurna dalam memori terbatas ** sangat penting untuk setiap klien yang berjalan lama.
Patrick Hughes
+1 untuk menyebutkan menjadi seagresif dengan penanganan kesalahan dalam pembuatan debug mungkin. Ingat bahwa pada perangkat keras konsol debug, terkadang Anda memiliki akses ke lebih banyak sumber daya daripada ritel. Anda mungkin ingin meniru kondisi tersebut pada perangkat keras dev dengan mengalokasikan objek debug secara eksklusif di ruang alamat di atas apa yang akan dimiliki perangkat ritel, dan terhempas saat ruang alamat yang setara dengan ritel habis.
FlintZA
5

Aplikasi ini biasanya diuji pada platform yang ditargetkan dengan skenario kasus terburuk dan Anda akan selalu siap untuk platform yang Anda targetkan. Idealnya aplikasi tidak boleh macet, tetapi selain optimasi untuk perangkat tertentu, ada sedikit pilihan ketika Anda menghadapi peringatan kehabisan memori.

Praktik terbaik adalah memiliki kolam yang telah dialokasikan sebelumnya dan permainan menggunakan dari awal semua memori yang dibutuhkan. Jika gim Anda memiliki maksimal 100 unit daripada memiliki kelompok untuk 100 unit dan hanya itu. Jika 100 unit melebihi persyaratan mem untuk satu perangkat yang ditargetkan maka Anda dapat mengoptimalkan unit untuk menggunakan lebih sedikit memori atau mengubah desain ke maksimum 90 unit. Seharusnya tidak ada kasus di mana Anda dapat membangun hal-hal yang tidak terbatas, harus selalu ada batasnya. Akan sangat buruk untuk permainan kotak pasir untuk digunakan newuntuk setiap contoh karena Anda tidak pernah dapat memprediksi penggunaan mem dan kerusakan jauh lebih buruk daripada batasan.

Juga desain game harus selalu mengingat perangkat yang ditargetkan terendah karena jika Anda mendasarkan desain Anda dengan hal-hal "tidak terbatas" di dalamnya maka akan jauh lebih sulit untuk menyelesaikan masalah memori atau mengubah desain nanti.

Raxvan
sumber
1

Nah, Anda dapat mengalokasikan sekitar 16 MiB (hanya untuk menjadi 100% yakin) pada saat startup atau bahkan pada .bsssaat kompilasi, dan menggunakan "pengalokasi aman", dengan tanda tangan seperti inline __attribute__((force_inline)) void* alloc(size_t size)( __attribute__((force_inline))adalah GCC / mingw-w64atribut yang memaksa inlining dari bagian kode kritis bahkan jika optimasi dinonaktifkan, meskipun itu harus diaktifkan untuk permainan) alih-alih mallocmencoba void* result = malloc(size)dan jika gagal, lepaskan cache, bebaskan memori cadangan (atau beri tahu kode lain untuk menggunakan .bssbenda itu tetapi itu di luar ruang untuk jawaban ini) dan flush data yang belum disimpan (simpan dunia ke disk, jika Anda menggunakan konsep potongan Minecraft, panggil sesuatu seperti saveAllModifiedChunks()). Kemudian, jika malloc(16777216)(mengalokasikan 16 MiB ini lagi) gagal (lagi, ganti dengan analog untuk .bss), hentikan game dan tunjukkanMessageBox(NULL, "*game name* couldn't continue because of lack of free memory, but your world was safely saved. Try closing background applications and restarting the game", "*Game name*: out of memory", MB_ICONERROR)atau alternatif spesifik platform. Menyatukan semuanya:

__attribute__((force_inline)) void* alloc(size_t size) {
    void* result = malloc(size); // Attempt to allocate normally
    if (!result) { // If the allocation failed...
        if (!reserveMemory) std::_Exit(); // If alloc() was called from forceFullSave() or reportOutOfMemory() and we again can't allocate, just quit, something is stealing all our memory. If we used the .bss approach, this wouldn't've been necessary.
        free(reserveMemory); // Global variable, pointer to the reserve 16 MiB allocated on startup
        forceFullSave(); // Saves the game
        reportOutOfMemory(); // Platform specific error message box code
        std::_Exit(); // Close silently
    } else return result;
}

Anda dapat menggunakan solusi yang sama dengan std::set_new_handler(myHandler)mana myHandleryang void myHandler(void)yang disebut ketika newgagal:

void newerrhandler() {
    if (!reserveMemory) std::_Exit(); // If new was called from forceFullSave() or reportOutOfMemory() and we again can't allocate, just quit, something is stealing all our memory. If we used the .bss approach, this wouldn't've been necessary.
    free(reserveMemory); // Global variable, pointer to the reserve 16 MiB allocated on startup
    forceFullSave(); // Saves the game
    reportOutOfMemory(); // Platform specific error message box code
    std::_Exit(); // Close silently
}

// In main ()...
std::set_new_handler(newerrhandler);
Vladislav Toncharov
sumber