메모리 부족 상태를 어떻게 준비합니까?


18

범위가 잘 정의 된 게임에서는이 작업이 쉬울 수 있지만 플레이어가 무엇이든 만들거나 만들 수있는 샌드 박스 게임에 대한 문제입니다 .

가능한 기술 :

  • 상한이있는 메모리 풀을 사용하십시오.
  • 더 이상 주기적으로 필요하지 않은 개체를 삭제하십시오.
  • 처음에 여분의 메모리를 할당하여 나중에 복구 메커니즘으로 해제 할 수 있습니다. 나는 약 2-4MB를 말할 것입니다.

이는 일반적으로 16GB PC와 달리 메모리가 제한 되는 모바일 / 콘솔 플랫폼 에서 발생할 가능성이 높습니다 . 메모리 할당 / 할당 취소를 완전히 제어하고 가비지 수집이 필요하지 않다고 가정합니다. 이것이 이것을 C ++로 태그하는 이유입니다.

유효 C ++ 항목 7 "메모리 부족 조건에 대비하십시오" 에 대해서는 이야기하고 있지 않습니다. 관련이 있음에도 불구하고 게임 개발과 관련하여 더 많은 답변을 원합니다. 사고.

문제를 요약 하기 위해 메모리 콘솔 / 모바일이 제한된 플랫폼을 대상으로 할 때 샌드 박스 게임의 메모리 부족 상태를 어떻게 준비합니까?


고장난 메모리 할당은 실제 RAM이 부족할 때 자동으로 하드 드라이브로 교체되기 때문에 최신 PC 운영 체제에서는 거의 사용되지 않습니다. 스와핑이 실제 RAM보다 훨씬 느리고 성능에 심각한 영향을 미치기 때문에 여전히 피해야 할 상황입니다 .
Philipp

@Philipp 예, 알고 있습니다. 그러나 내 질문은 콘솔 및 모바일과 같은 메모리 제한 장치에 대한 것입니다.
concept3d

이것은 상당히 광범위한 질문입니다 (그리고 말한 방식으로 설문 조사합니다). 단일 상황에보다 구체적으로 범위를 좁힐 수 있습니까?
MichaelHouse

@ Byte56 질문을 편집했습니다. 나는 그것이 더 정의 된 범위를 갖기를 바랍니다.
concept3d

답변:


16

일반적으로 메모리 부족을 처리하지 않습니다. 게임만큼 크고 복잡한 소프트웨어의 유일한 옵션은 가능한 빨리 (특히 디버그 빌드에서) 메모리 할당 기에서 충돌 / 어설 션 / 종료하는 것입니다. 메모리 부족 조건은 일부 핵심 시스템 소프트웨어 또는 서버 소프트웨어에서 테스트되고 처리되지만 경우에 따라 다른 곳에서는 처리되지 않습니다.

상단 메모리 캡이있는 경우에는 그보다 많은 양의 메모리가 필요하지 않도록하십시오. 예를 들어, 한 번에 최대 NPC 수를 유지하고 한도에 도달하면 새로운 비 필수 NPC 생성을 중지 할 수 있습니다. 필수 NPC의 경우 비 필수 NPC를 대체하거나 설계자가 알고있는 필수 NPC를위한 별도의 풀 / 캡을 가질 수 있습니다 (예 : 3 개의 필수 NPC를 보유 할 수있는 경우 설계자는 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에서도 쉽게 충돌 할 수 있습니다 (128 비트 RAM이 장착 된 PC가 64 개인 경우에도 일반 32 비트 앱의 주소 공간은 2-3GB로 제한됨을 기억하십시오) 비트 OS 및 하드웨어를 사용하면 더 많은 32 비트 응용 프로그램을 동시에 실행할 수 있지만 32 비트 바이너리가 더 큰 주소 공간을 갖도록하려면 아무것도 할 수 없습니다. 결국, 모든 경우에 무한한 메모리 공간이 필요한 매우 유연한 게임 세계가 있거나 항상 제한된 메모리 (또는 그 사이의 다른 곳)에서 완벽하게 작동하는 매우 제한적이고 통제 된 세계가 있습니다.


이 답변에 +1 나는 Sean의 스타일과 개별 메모리 풀을 사용하여 작동하는 두 개의 시스템을 작성했으며 둘 다 프로덕션에서 잘 작동했습니다. 첫 번째는 스폰 너로 커브의 출력을 최대 한도 셧 오프로 롤백 했으므로 플레이어는 갑작스러운 감소를 알 수 없었습니다 (총 처리량은 해당 안전 마진으로 낮아졌습니다). 두 번째는 할당 실패가 퍼지 및 재 할당을 강제한다는 점에서 청크와 관련이 있습니다. ** 제한된 메모리에서 항상 완벽하게 작동하는 매우 제한적이고 통제 된 세계 **는 장기 실행 클라이언트에게 매우 중요합니다.
Patrick Hughes

디버그 빌드에서 오류 처리를 최대한 적극적으로 언급 한 경우 +1 디버그 콘솔 하드웨어에서는 소매보다 더 많은 리소스에 액세스 할 수 있습니다. 소매점 디바이스보다 주소 공간에 독점적으로 디버그 오브젝트를 할당하고 소매점과 동등한 주소 공간이 사용되면 충돌하여 개발 하드웨어에서 이러한 조건을 모방 할 수 있습니다.
FlintZA

5

응용 프로그램은 일반적으로 최악의 시나리오로 대상 플랫폼에서 테스트되며 대상 플랫폼에 항상 준비됩니다. 이상적으로는 응용 프로그램이 중단되지 않아야하지만 특정 장치에 대한 최적화 외에는 메모리 부족 경고가 표시 될 때 선택의 여지가 거의 없습니다.

가장 좋은 방법은 미리 할당 된 풀을 가지고 게임은 처음부터 필요한 모든 메모리를 사용하는 것입니다. 게임의 수영장이 100 개 이상인 것보다 최대 100 개가 있다면 그게 전부입니다. 하나의 대상 장치에 대해 100 단위가 mem 요구 사항을 초과하면 메모리를 덜 사용하거나 설계를 최대 90 단위로 변경하도록 장치를 최적화 할 수 있습니다. 무제한으로 물건을 만들 수있는 경우가 없어야하며 항상 한계가 있어야합니다. 샌드 박스 게임이 new각 인스턴스 에 사용하는 것은 매우 나쁜 일입니다. mem 사용량을 예측할 수 없으며 충돌이 제한보다 훨씬 나쁘기 때문입니다.

또한 게임 디자인은 항상 대상이 가장 낮은 장치를 염두에 두어야합니다. 디자인에 "무제한"항목을 기반으로하면 메모리 문제를 해결하거나 나중에 디자인을 변경하기가 훨씬 더 어려워지기 때문입니다.


1

글쎄, 당신은 시작시 또는 16 MiB 크기 (단지 확인 100 % 예정)에 대해 할당 할 수있는 .bss컴파일시와 같은 서명, "안전한 할당"을 사용 inline __attribute__((force_inline)) void* alloc(size_t size)( __attribute__((force_inline))GCC는 /입니다 mingw-w64힘이 중요한 코드 섹션의 인라인 것을 속성 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.