คุณเตรียมตัวอย่างไรสำหรับเงื่อนไขหน่วยความจำไม่เพียงพอ?


18

นี้จะเป็นเรื่องง่ายสำหรับเกมที่มีขอบเขตกำหนดไว้อย่างดี แต่คำถามคือเกี่ยวกับเกม sandbox ที่ที่ผู้เล่นจะได้รับอนุญาตให้สร้างและสร้างอะไร

เทคนิคที่เป็นไปได้:

  • ใช้พูลหน่วยความจำที่มีขีด จำกัด สูงสุด
  • ลบวัตถุที่ไม่ต้องการเป็นระยะ ๆ
  • จัดสรรจำนวนหน่วยความจำเพิ่มเติมที่จุดเริ่มต้นเพื่อให้สามารถเป็นอิสระในภายหลังเป็นกลไกการกู้คืน ฉันจะบอกว่าประมาณ 2-4 MBs

สิ่งนี้มีแนวโน้มที่จะเกิดขึ้นในแพลตฟอร์มมือถือ / คอนโซลซึ่งหน่วยความจำมักจะถูก จำกัด ไม่เหมือนกับพีซีขนาด 16 GB ของคุณ ฉันสมมติว่าคุณสามารถควบคุมการจัดสรร / การจัดสรรคืนหน่วยความจำได้อย่างสมบูรณ์และไม่มีการรวบรวมขยะ นั่นเป็นเหตุผลที่ฉันกำลังติดแท็กสิ่งนี้เป็น C ++

โปรดทราบว่าฉันไม่ได้พูดถึงรายการ C ++ ที่มีประสิทธิภาพ 7 "เตรียมพร้อมสำหรับเงื่อนไขหน่วยความจำไม่เพียงพอ"แม้ว่าจะเกี่ยวข้องกัน แต่ฉันต้องการดูคำตอบเพิ่มเติมเกี่ยวกับการพัฒนาเกมซึ่งคุณมักจะควบคุมสิ่งที่มากกว่า สิ่งที่เกิดขึ้น

เพื่อสรุปคำถามคุณจะเตรียมตัวอย่างไรสำหรับเงื่อนไขหน่วยความจำไม่เพียงพอสำหรับเกมแซนด์บ็อกซ์เมื่อคุณกำหนดเป้าหมายแพลตฟอร์มด้วยคอนโซลหน่วยความจำ / อุปกรณ์เคลื่อนที่ จำกัด


การจัดสรรหน่วยความจำที่ล้มเหลวนั้นค่อนข้างหายากในระบบปฏิบัติการพีซีสมัยใหม่เนื่องจากจะสลับไปยังฮาร์ดไดรฟ์โดยอัตโนมัติเมื่อหน่วยความจำ RAM ไม่เพียงพอ ยังคงเป็นสถานการณ์ที่ควรหลีกเลี่ยงเพราะแลกเปลี่ยนเป็นมากช้ากว่า RAM ที่มีอยู่จริงอย่างรุนแรงและจะส่งผลกระทบต่อประสิทธิภาพการทำงาน
Philipp

@ ฟิลิปใช่ฉันรู้ แต่คำถามของฉันมีต่ออุปกรณ์ จำกัด หน่วยความจำเช่นคอนโซลและโทรศัพท์มือถือฉันคิดว่าฉันพูดถึงเรื่องนั้น
concept3d

นี่เป็นคำถามที่ค่อนข้างกว้าง (และเป็นแบบสำรวจความคิดเห็น) คุณสามารถ จำกัด ขอบเขตให้แคบลงเพื่อให้เฉพาะกับสถานการณ์เดียวมากขึ้นได้หรือไม่?
MichaelHouse

@ Byte56 ฉันแก้ไขคำถาม ฉันหวังว่ามันจะมีขอบเขตที่ชัดเจนมากขึ้นในขณะนี้
concept3d

คำตอบ:


16

โดยทั่วไปคุณไม่ได้จัดการกับหน่วยความจำไม่เพียงพอ ตัวเลือกที่มีเหตุผลเพียงอย่างเดียวในซอฟต์แวร์ที่มีขนาดใหญ่และซับซ้อนเช่นเดียวกับเกมคือการชน / ยืนยัน / ยุติในตัวจัดสรรหน่วยความจำของคุณโดยเร็วที่สุดเท่าที่จะทำได้ เงื่อนไขหน่วยความจำไม่เพียงพอได้รับการทดสอบและจัดการกับซอฟต์แวร์ระบบหลักหรือซอฟต์แวร์เซิร์ฟเวอร์ในบางกรณี แต่มักจะไม่ได้อยู่ที่อื่น

เมื่อคุณมีฝาปิดหน่วยความจำส่วนบนคุณเพียงแค่ให้แน่ใจว่าคุณไม่ต้องการหน่วยความจำมากกว่านั้น คุณสามารถรักษาจำนวน NPC ที่อนุญาตได้สูงสุดต่อครั้งและเพียงแค่หยุดการวางไข่ NPC ที่ไม่จำเป็นอีกครั้งเมื่อการโจมตีสูงสุด สำหรับ NPC ที่สำคัญคุณสามารถให้พวกมันแทนที่อันที่ไม่จำเป็นหรือมีพูล / หมวกแยกต่างหากสำหรับ NPC ที่จำเป็นที่นักออกแบบของคุณรู้ที่จะออกแบบรอบ ๆ (เช่นถ้าคุณสามารถมี NPC ที่จำเป็น 3 ชิ้นเท่านั้นนักออกแบบจะไม่ใส่มากกว่า 3 ใน พื้นที่ / ชิ้นส่วน - เครื่องมือที่ดีจะช่วยให้นักออกแบบทำสิ่งนี้ได้อย่างถูกต้องและการทดสอบเป็นสิ่งจำเป็นอย่างยิ่ง)

ระบบสตรีมที่ดีจริงๆก็มีความสำคัญเช่นกันโดยเฉพาะเกม Sandbox คุณไม่จำเป็นต้องเก็บ NPC และรายการทั้งหมดไว้ในหน่วยความจำ ในขณะที่คุณเคลื่อนผ่านช่องว่างของโลกชิ้นใหม่จะถูกสตรีมเข้าและชิ้นส่วนเก่าจะถูกสตรีม โดยทั่วไปจะรวม NPC และรายการเช่นเดียวกับภูมิประเทศ ต้องตั้งค่าการออกแบบและวิศวกรรมขีด จำกัด ของรายการโดยคำนึงถึงระบบนี้เนื่องจากรู้ว่าจะต้องเก็บชิ้นส่วนเก่า ๆ ไว้รอบ X และโหลดชิ้นใหม่ Y เชิงรุกอย่างต่อเนื่องดังนั้นเกมจะต้องมีพื้นที่เพื่อให้ทั้งหมด ข้อมูลของ X + Y + 1 ชิ้นในหน่วยความจำ

เกมบางเกมพยายามจัดการกับสถานการณ์ที่ไม่อยู่ในความทรงจำด้วยวิธีการส่งผ่านสองทาง โปรดทราบว่าเกมส่วนใหญ่มีข้อมูลแคชที่ไม่จำเป็นในทางเทคนิคมากมาย (เช่นก้อนเก่าที่กล่าวถึงข้างต้น) และการจัดสรรหน่วยความจำอาจทำสิ่งต่อไปนี้:

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)

นี่เป็นมาตรการสุดท้ายที่จะจัดการกับสถานการณ์ที่ไม่คาดคิดในการเปิดตัว แต่ในระหว่างการดีบักและการทดสอบคุณควรจะพังทันที คุณไม่ต้องการที่จะพึ่งพาสิ่งนี้ (โดยเฉพาะอย่างยิ่งเนื่องจากการทิ้งแคชอาจส่งผลต่อประสิทธิภาพที่ร้ายแรง)

คุณอาจลองถ่ายสำเนาข้อมูลที่มีความละเอียดสูงบางตัวอย่างเช่นคุณอาจถ่ายโอนข้อมูลระดับพื้นผิว mipmap ความละเอียดสูงกว่าหากคุณใช้หน่วยความจำ GPU ต่ำ (หรือหน่วยความจำใด ๆ ในสถาปัตยกรรมหน่วยความจำที่ใช้ร่วมกัน) ซึ่งมักจะต้องใช้งานสถาปัตยกรรมจำนวนมากเพื่อสร้างความคุ้มค่า

โปรดทราบว่าเกมแซนด์บ็อกซ์ที่ไม่ จำกัด จำนวนมากสามารถชนกันได้อย่างง่ายดายแม้กระทั่งบนพีซี (โปรดจำไว้ว่าแอพ 32 บิตทั่วไปมีพื้นที่ที่อยู่ 2-3 GB จำกัด แม้ว่าคุณจะมีพีซีที่มี RAM ขนาด 128GB หรือ 64- ระบบปฏิบัติการบิตและฮาร์ดแวร์ช่วยให้แอปแบบ 32 บิตเพิ่มเติมสามารถทำงานพร้อมกันได้ แต่ไม่สามารถทำอะไรได้เลยเพื่อให้ไบนารีแบบ 32 บิตมีพื้นที่ที่อยู่ที่ใหญ่กว่า) ในท้ายที่สุดคุณมีโลกแห่งเกมที่ยืดหยุ่นอย่างมากซึ่งจะต้องใช้พื้นที่หน่วยความจำที่ไม่ จำกัด เพื่อให้ทำงานได้ในทุกกรณีหรือคุณมีโลกที่ จำกัด และควบคุมได้ซึ่งทำงานได้อย่างสมบูรณ์แบบในหน่วยความจำที่มีขอบเขต


+1 สำหรับคำตอบนี้ ฉันเขียนสองระบบที่ทำงานโดยใช้สไตล์ของฌอนและพูลหน่วยความจำแบบแยกส่วนและทั้งคู่ก็ทำงานได้ดีในการผลิต อย่างแรกคือ spawner ที่หมุนเอาท์พุทบนเส้นโค้งไปที่การ จำกัด การปิดสูงสุดดังนั้นผู้เล่นจะไม่สังเกตเห็นการลดลงอย่างกะทันหัน (คิดว่าปริมาณงานทั้งหมดนั้นลดลงด้วยค่าความปลอดภัยนั้น) ข้อสองเกี่ยวข้องกับชิ้นส่วนที่การจัดสรรที่ล้มเหลวจะบังคับให้ล้างและจัดสรรใหม่ ฉันรู้สึกว่า ** โลกที่มีขอบเขต จำกัด และถูกควบคุมซึ่งทำงานได้อย่างสมบูรณ์แบบในความทรงจำที่มีขอบเขตเสมอ ** มีความสำคัญสำหรับลูกค้าที่ใช้งานมานาน
Patrick Hughes

+1 สำหรับการกล่าวถึงความก้าวร้าวและการจัดการข้อผิดพลาดในการสร้างข้อบกพร่องที่เป็นไปได้ โปรดจำไว้ว่าในฮาร์ดแวร์คอนโซล debug บางครั้งคุณสามารถเข้าถึงทรัพยากรได้มากกว่าร้านค้าปลีก คุณอาจต้องการเลียนแบบเงื่อนไขเหล่านั้นบนฮาร์ดแวร์ dev โดยการจัดสรรวัตถุการตรวจแก้จุดบกพร่องเฉพาะในพื้นที่ที่อยู่ด้านบนสิ่งที่อุปกรณ์ค้าปลีกจะมีและหยุดทำงานเมื่อมีการใช้พื้นที่ที่อยู่เทียบเท่าค้าปลีก
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.