メモリー不足状態にどのように準備しますか?


18

これは、明確に定義された範囲のゲームにとっては簡単ですが、問題はサンドボックスゲームについてであり、プレイヤーは何でも作成および構築できます。

可能なテクニック:

  • 上限のあるメモリプールを使用します。
  • 定期的に不要になったオブジェクトを削除します。
  • 回復メカニズムとして後で解放できるように、最初に余分な量のメモリを割り当てます。約2〜4 MBです。

これは、16 GB PCとは異なり、メモリが通常制限されているモバイル/コンソールプラットフォームで発生する可能性が高くなります。メモリの割り当て/割り当て解除を完全に制御でき、ガベージコレクションが関与していないことを前提としています。これが、これをC ++としてタグ付けする理由です。

私が話していないですので、予めご了承ください効果的なC ++項目7「メモリ不足の条件のために準備して、」それは、関連だにもかかわらず、私はより多くのあなたが、通常は何をより詳細に制御を持っているゲーム開発に関連する答えを見たいのですが、ハプニング。

質問をまとめると、メモリコンソール/モバイルが限られているプラ​​ットフォームをターゲットにしている場合、サンドボックスゲームのメモリ不足状態どのように対応しますか?


失敗したメモリ割り当ては、物理RAMが不足すると自動的にハードドライブにスワップされるため、最新のPCオペレーティングシステムでは非常にまれです。スワップは物理RAMよりもはるかに遅く、パフォーマンスに重大な影響を与えるため、回避すべき状況です。
フィリップ

@Philippはい、知っています。しかし、私の質問は、コンソールやモバイルなどのメモリが限られたデバイスに関するものです。
concept3d

これはかなり広範な質問です(そして、一種の世論調査の言い回しです)。単一の状況により具体的になるように範囲を少し絞り込めますか?
マイケルハウス

@ Byte56質問を編集しました。スコープがより定義されていることを望みます。
concept3d

回答:


16

通常、メモリ不足は処理しません。ゲームのように大きく複雑なソフトウェアの唯一の健全なオプションは、できるだけ早くメモリアロケータでクラッシュ/アサート/終了することです(特にデバッグビルドの場合)。メモリ不足状態は、一部のコアシステムソフトウェアまたはサーバーソフトウェアでテストおよび処理されますが、通常はそうではありません。

上限のメモリキャップがある場合は、代わりに、その量以上のメモリを必要としないことを確認してください。たとえば、一度に最大数の許可されたNPCを保持し、その上限に達すると新しい非必須NPCの生成を停止できます。エッセンシャルNPCについては、非エッセンシャルNPCを置き換えるか、デザイナーが設計する必要のあるエッセンシャルNPC用の別のプール/キャップを持つことができます(たとえば、エッセンシャルNPCsaを3つしか持てない場合、デザイナーは3つ以上を入れません)エリア/チャンク-優れたツールは設計者がこれを適切に行うのに役立ち、テストはもちろん不可欠です。

サンドボックスゲームでは、本当に優れたストリーミングシステムも重要です。すべてのNPCとアイテムをメモリに保持する必要はありません。世界のチャンクを移動すると、新しいチャンクがストリームされ、古いチャンクがストリームされます。これらには通常、NPCとアイテム、地形が含まれます。アイテム制限の設計とエンジニアリングの上限は、このシステムを念頭に置いて設定する必要があります。最大でX個の古いチャンクが保持され、プロアクティブにロードされるY個の新しいチャンクがロードされるため、ゲームにはすべてを保持するスペースが必要ですメモリ内のX + Y + 1チャンクのデータ。

一部のゲームは、2パスアプローチでメモリ不足の状況を処理しようとします。ほとんどのゲームには多くの技術的に不要なキャッシュデータ(上記の古いチャンクなど)があり、メモリの割り当ては次のようなことを行うことに注意してください。

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)

これは、リリース中の予期しない状況に対処するための最後の手段ですが、デバッグおよびテスト中は、おそらくただちにクラッシュするはずです。この種のものに依存する必要はありません(特にキャッシュのダンプはパフォーマンスに重大な影響を与える可能性があるため)。

また、一部のデータの高解像度コピーのダンプを検討することもできます。たとえば、GPUメモリ(または共有メモリアーキテクチャのメモリ)が不足している場合、高解像度のテクスチャのミップマップレベルをダンプできます。ただし、これを実現するには、通常、多くの建築作業が必要です。

一部の非常に無制限のサンドボックスゲームは、PCでも簡単にクラッシュする可能性があることに注意してください(一般的な32ビットアプリでは、128 GBのRAMを搭載したPCでも、アドレス空間が2-3 GBに制限されていることに注意してください。ビットOSとハードウェアを使用すると、より多くの32ビットアプリを同時に実行できますが、32ビットバイナリのアドレス空間を大きくすることはできません)。最終的に、すべての場合に実行するために無制限のメモリ空間を必要とする非常に柔軟なゲームワールドがあるか、または制限されたメモリ(またはその間の何か)で常に完全に動作する非常に制限され制御されたワールドがあります。


この回答の+1。Seanのスタイルと個別のメモリプールを使用して動作する2つのシステムを作成しましたが、どちらも本番環境で正常に動作しました。1つ目は、プレイヤーが突然の減少に気付かないように、曲線上の出力を最大制限シャットオフにロールバックするスポーナーでした(合計スループットはその安全マージンによって低下したと考えられていました)。2番目はチャンクに関連しており、割り当てに失敗すると強制的にパージと再割り当てが行われます。**限られたメモリ内で常に完全に動作する**非常に制限され制御された世界**は、長期実行クライアントにとって不可欠であると感じています。
パトリックヒューズ

デバッグビルドでのエラー処理を可能な限り積極的に行うことについて言及する場合は、+ 1。デバッグコンソールハードウェアでは、小売よりも多くのリソースにアクセスできる場合があることに注意してください。小売デバイスが持つものよりも上のアドレス空間でのみデバッグオブジェクトを割り当て、小売相当のアドレス空間が使い果たされるとクラッシュすることにより、開発ハードウェアでこれらの条件を模倣することができます。
フリントザ

5

通常、アプリケーションは、最悪のシナリオのターゲットプラットフォームでテストされ、ターゲットのプラットフォームに常に対応できます。理想的には、アプリケーションがクラッシュすることはありませんが、特定のデバイスに対する最適化以外に、メモリ不足の警告に直面した場合にはほとんど選択肢がありません。

ベストプラクティスは、事前に割り当てられたプールを用意し、ゲームで必要なすべてのメモリを最初から使用することです。ゲームの最大ユニット数が100ユニットの場合、100ユニットのプールがある場合はそれだけです。100個のユニットが1つのターゲットデバイスのmem要件を超える場合、ユニットを最適化して、使用するメモリを減らすか、デザインを最大90ユニットに変更できます。無制限のものを構築できる場合はないはずです。常に制限があるはずです。newmemの使用量を予測することはできず、クラッシュは制限よりも最悪であるため、サンドボックスゲームが各インスタンスで使用することは非常に悪いでしょう。

また、ゲームデザインは常にターゲットの最も低いデバイスを念頭に置いておく必要があります。「無制限」のものをデザインのベースにすると、メモリの問題を解決したり、後でデザインを変更したりするのがはるかに難しくなるためです。


1

さて、起動時または.bssコンパイル時にも約16 MiB(ちょうど100%確実)を割り当て、「inline __attribute__((force_inline)) void* alloc(size_t size)__attribute__((force_inline))mingw-w64重要なコードセクションのインライン化を強制するGCC / 属性です)のような署名を持つ「安全なアロケーター」を使用できます最適化が代わりに)彼らはゲームのために有効にする必要があるにも関わらず、無効になっている場合でも、mallocその試みvoid* result = malloc(size)、それが失敗した場合、ドロップキャッシュ、予備のメモリを解放する(または使用する他のコードを伝える.bss事をそれはこの答えの範囲外です)と未保存のデータをフラッシュします(Minecraftのようなチャンクの概念を使用している場合は、ディスクに世界を保存し、のようなものを呼び出しますsaveAllModifiedChunks())。次に、malloc(16777216)(これらの16 MiBを再度割り当てる)が失敗した場合(再度、アナログに置き換えます.bss)、ゲームを終了して表示しますMessageBox(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)またはプラットフォーム固有の代替。すべてを一緒に入れて:

__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;
}

あなたが同様のソリューションを使用することができますstd::set_new_handler(myHandler)どこmyHandlerされvoid myHandler(void)たときには呼ばれていますnew失敗します。

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);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.