Wie bereiten Sie sich auf Speicherprobleme vor?

18

Dies kann für Spiele mit genau definiertem Umfang einfach sein, aber die Frage betrifft Sandbox-Spiele, bei denen der Spieler alles erstellen und bauen darf .

Mögliche Techniken:

  • Verwenden Sie Speicherpools mit Obergrenze.
  • Löschen Sie regelmäßig nicht mehr benötigte Objekte.
  • Weisen Sie zu Beginn zusätzliche Speicherkapazität zu, damit diese später als Wiederherstellungsmechanismus freigegeben werden kann. Ich würde sagen, um 2-4 MB.

Dies ist bei Mobil- / Konsolenplattformen wahrscheinlicher, bei denen der Speicher im Gegensatz zu Ihrem 16-GB-PC normalerweise begrenzt ist. Ich gehe davon aus, dass Sie die vollständige Kontrolle über die Speicherzuweisung / Freigabe haben und keine Speicherbereinigung erforderlich ist. Das ist der Grund, warum ich dies als C ++ etikettiere.

Bitte beachten Sie, dass ich nicht über Effective C ++, Punkt 7 "Seien Sie auf Bedingungen mit unzureichendem Arbeitsspeicher vorbereitet" , spreche , obwohl dies relevant ist, würde ich gerne eine Antwort in Bezug auf die Spieleentwicklung sehen, bei der Sie normalerweise mehr Kontrolle darüber haben, was ist Ereignis.

Um die Frage zusammenzufassen: Wie bereiten Sie sich auf nicht genügend Arbeitsspeicher für Sandbox-Spiele vor, wenn Sie auf eine Plattform mit eingeschränkter Speicherkonsole / Handy abzielen?

concept3d
quelle
Fehlgeschlagene Speicherzuweisungen sind bei modernen PC-Betriebssystemen ziemlich selten, da sie automatisch auf die Festplatte übertragen werden, wenn der physische Arbeitsspeicher knapp wird. Eine Situation, die vermieden werden sollte, da das Auslagern viel langsamer ist als der physische Arbeitsspeicher und die Leistung erheblich beeinträchtigt.
Philipp
@Philipp ja ich weiß. Aber meine Frage ist mehr nach speicherbeschränkten Geräten wie Konsolen und Handys, von denen ich glaube, dass ich das erwähnt habe.
concept3d
Dies ist eine ziemlich weit gefasste Frage (und eine Art Umfrage, wie sie formuliert ist). Können Sie den Geltungsbereich etwas eingrenzen, um ihn auf eine bestimmte Situation abzustimmen?
MichaelHouse
@Byte56 Ich habe die Frage bearbeitet. Ich hoffe, es hat jetzt einen genaueren Geltungsbereich.
concept3d

Antworten:

16

Im Allgemeinen gehen Sie nicht mit zu wenig Arbeitsspeicher um. Die einzig vernünftige Option in Software, die so umfangreich und komplex wie ein Spiel ist, besteht darin, so schnell wie möglich in Ihrem Speicher-Allokator abzustürzen / zu bestätigen / zu beenden (insbesondere bei Debug-Builds). Zu wenig Arbeitsspeicher wird in einigen Fällen auf Kernsystem- oder Serversoftware getestet und verarbeitet, in der Regel jedoch nicht an anderer Stelle.

Wenn Sie eine höhere Speicherkapazität haben, stellen Sie stattdessen sicher, dass Sie nie mehr als diese Speicherkapazität benötigen. Sie können beispielsweise eine maximale Anzahl zulässiger NPCs gleichzeitig behalten und einfach aufhören, neue nicht-essentielle NPCs zu erzeugen, sobald diese Obergrenze erreicht ist. Für wichtige NPCs können Sie entweder festlegen, dass sie nicht wichtige ersetzen, oder Sie können einen separaten Pool / Cap für wichtige NPCs einrichten, um die sich Ihre Designer kümmern (z. B. wenn Sie nur 3 wichtige NPCs haben können, werden die Designer nicht mehr als 3 einsetzen Ein Bereich / Block - gute Werkzeuge helfen Designern dabei, dies richtig zu tun, und Tests sind natürlich unerlässlich.

Ein wirklich gutes Streaming-System ist auch wichtig, insbesondere für Sandbox-Spiele. Sie müssen nicht alle NPCs und Gegenstände im Speicher behalten. Während Sie sich durch Teile der Welt bewegen, werden neue Teile und alte Teile gestreamt. Dazu gehören im Allgemeinen NSCs und Gegenstände sowie Gelände. Design- und Konstruktionsbeschränkungen für Item-Limits müssen unter Berücksichtigung dieses Systems festgelegt werden. Dabei ist zu beachten, dass höchstens X alte Chunks beibehalten und proaktiv geladen werden. Y neue Chunks werden geladen, sodass das Spiel Platz für alle behalten muss Die Daten von X + Y + 1 Blöcken im Speicher.

Einige Spiele versuchen, Situationen mit unzureichendem Arbeitsspeicher mit einem Ansatz mit zwei Durchläufen zu behandeln. Denken Sie daran, dass die meisten Spiele viele technisch unnötige zwischengespeicherte Daten enthalten (z. B. die oben genannten alten Blöcke), und eine Speicherzuweisung kann etwa Folgendes bewirken:

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)

Dies ist eine letzte Maßnahme, um mit unerwarteten Situationen in Releases umzugehen. Während des Debuggens und Testens sollten Sie jedoch wahrscheinlich sofort abstürzen. Sie möchten sich nicht auf solche Dinge verlassen müssen (insbesondere, weil das Löschen der Caches schwerwiegende Auswirkungen auf die Leistung haben kann).

Sie könnten auch in Betracht ziehen, hochauflösende Kopien einiger Daten zu sichern. Beispielsweise könnten Sie die höher aufgelösten Mipmap-Ebenen von Texturen sichern, wenn Ihnen der GPU-Speicher (oder ein Speicher in einer Architektur mit gemeinsamem Speicher) ausgeht. Dies erfordert jedoch in der Regel viel architektonische Arbeit, um es wert zu machen.

Beachten Sie, dass einige sehr unbegrenzte Sandbox-Spiele auch auf dem PC ziemlich einfach zum Absturz gebracht werden können (denken Sie daran, dass die gängigen 32-Bit-Apps nur einen Adressraum von 2 bis 3 GB haben, selbst wenn Sie einen PC mit 128 GB RAM haben; ein 64-Bit-Computer). Bit-Betriebssystem und Hardware ermöglichen die gleichzeitige Ausführung von mehr 32-Bit-Apps, können jedoch nichts tun, um eine 32-Bit-Binärdatei mit einem größeren Adressraum auszustatten. Am Ende haben Sie entweder eine sehr flexible Spielwelt, die in jedem Fall unbegrenzten Speicherplatz benötigt, oder Sie haben eine sehr begrenzte und kontrollierte Welt, die in begrenztem Speicher (oder irgendwo dazwischen) immer perfekt funktioniert.

Sean Middleditch
quelle
+1 für diese Antwort. Ich habe zwei Systeme geschrieben, die mit Seans Stil und diskreten Speicherpools arbeiten, und beide funktionierten gut in der Produktion. Der erste war ein Spawner, der die Leistung in einer Kurve auf das maximale Abschaltlimit zurücksetzte, sodass der Spieler niemals eine plötzliche Reduzierung bemerkte (obwohl der Gesamtdurchsatz um diese Sicherheitsmarge gesenkt wurde). Das zweite Problem bezog sich auf die Chunks, da eine fehlgeschlagene Zuordnung Säuberungen und Neuzuordnungen erzwingen würde. Ich bin der Meinung, dass ** eine sehr begrenzte und kontrollierte Welt, die immer perfekt in begrenztem Speicher funktioniert **, für jeden langjährigen Kunden von entscheidender Bedeutung ist.
Patrick Hughes
+1, um zu erwähnen, dass die Fehlerbehandlung in Debug-Builds so aggressiv wie möglich ist. Denken Sie daran, dass Sie auf der Hardware der Debug-Konsole manchmal Zugriff auf mehr Ressourcen als im Einzelhandel haben. Möglicherweise möchten Sie diese Bedingungen auf der Dev-Hardware nachahmen, indem Sie Debug-Objekte ausschließlich im Adressraum oberhalb der Geräte des Einzelhandels zuweisen und abstürzen, wenn der entsprechende Adressraum des Einzelhandels belegt ist.
FlintZA
5

Die Anwendung wird normalerweise auf der Zielplattform mit den Worst-Case-Szenarien getestet und Sie sind immer auf die Plattform vorbereitet, auf die Sie abzielen. Im Idealfall sollte die Anwendung niemals abstürzen, aber abgesehen von der Optimierung für bestimmte Geräte stehen Ihnen nur wenige Optionen zur Verfügung, wenn eine Warnung zu wenig Arbeitsspeicher angezeigt wird.

Die beste Vorgehensweise besteht darin, Pools vorab zuzuweisen, und das Spiel verwendet von Anfang an den gesamten benötigten Speicher. Wenn Ihr Spiel maximal 100 Einheiten hat, haben Sie einen Pool für 100 Einheiten und das wars. Wenn 100 Einheiten die Speicheranforderungen für ein Zielgerät überschreiten, können Sie die Einheit optimieren, um weniger Speicher zu verwenden, oder das Design auf maximal 90 Einheiten ändern. Es sollte keinen Fall geben, in dem Sie unbegrenzte Dinge bauen können, es sollte immer eine Grenze geben. Es wäre sehr schlecht, wenn ein Sandbox-Spiel newfür jede Instanz verwendet würde, da Sie die Mem-Nutzung niemals vorhersagen können und ein Absturz viel schlimmer ist als eine Einschränkung.

Außerdem sollte das Spieldesign immer die Geräte mit den niedrigsten Zielen berücksichtigen, denn wenn Sie Ihr Design mit "unbegrenzten" Dingen ausstatten, wird es viel schwieriger, die Speicherprobleme zu lösen oder das Design später zu ändern.

Raxvan
quelle
1

Nun, Sie können ungefähr 16 MiB (nur um 100% sicher zu sein) beim Start oder sogar .bssbeim Kompilieren zuweisen und einen "sicheren Allokator" mit einer Signatur wie inline __attribute__((force_inline)) void* alloc(size_t size)( __attribute__((force_inline))ist ein GCC / mingw-w64Attribut, das das Inlinen kritischer Codeabschnitte erzwingt) verwenden Auch wenn Optimierungen deaktiviert sind (auch wenn sie für Spiele aktiviert sein sollten) malloc, versuchen void* result = malloc(size)Sie stattdessen , und falls dies fehlschlägt, löschen Sie die Caches, geben Sie den freien Speicher frei (oder teilen Sie anderen Code mit, das .bssDing zu verwenden , aber das ist für diese Antwort nicht möglich) Leeren Sie nicht gespeicherte Daten (speichern Sie die Welt auf der Festplatte, wenn Sie ein Minecraft-ähnliches Konzept von Chunks verwenden, rufen Sie so etwas wie auf saveAllModifiedChunks()). Wenn das malloc(16777216)(erneutes Zuweisen dieser 16 MiB) fehlschlägt (erneut durch analog für ersetzen .bss), beenden Sie das Spiel und zeigen SieMessageBox(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)oder eine plattformspezifische Alternative. Alles zusammenfassen:

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

Sie können eine ähnliche Lösung mit verwenden , std::set_new_handler(myHandler)wo myHandlerwird void myHandler(void)das genannt , wenn newfehlschlägt:

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
quelle