ฉันจะบัญชี GC เมื่อสร้างเกมด้วย Unity ได้อย่างไร


15

* เท่าที่ฉันรู้ Unity3D สำหรับ iOS ขึ้นอยู่กับรันไทม์ของโมโนและโมโนมีเพียงเครื่องหมายทั่วไปและการกวาด GC

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

ดังนั้นฉันจะหลีกเลี่ยงเวลา GC นี้ได้อย่างไรเมื่อใช้ Unity3D โดยไม่มีประสิทธิภาพลดลงอย่างมาก


3
Profiler Unity Pro ล่าสุดมีจำนวนขยะที่ถูกสร้างขึ้นในการเรียกใช้ฟังก์ชันบางอย่าง คุณสามารถใช้เครื่องมือนี้เพื่อช่วยดูสถานที่อื่น ๆ ที่อาจสร้างขยะที่อาจไม่ชัดเจนในทันที
Tetrad

คำตอบ:


18

โชคดีที่คุณชี้ให้เห็นว่าCOMPACT Mono build ใช้ generational GC (ในทางตรงข้ามกับ Microsoft เช่น WinMo / WinPhone / XBox ที่เพิ่งรักษารายชื่อไว้)

หากเกมของคุณง่าย GC ควรจัดการได้ดี แต่นี่คือตัวชี้บางอย่างที่คุณอาจต้องการตรวจสอบ

การเพิ่มประสิทธิภาพก่อนวัยอันควร

ก่อนอื่นให้แน่ใจว่านี่เป็นปัญหาสำหรับคุณจริง ๆ ก่อนที่จะลองแก้ไข

การรวมประเภทอ้างอิงราคาแพง

คุณควรพูประเภทอ้างอิงที่คุณสร้างบ่อยหรือมีโครงสร้างที่ลึก ตัวอย่างของแต่ละคนจะเป็น:

  • สร้างมักจะ: เป็นBulletวัตถุในเกมกระสุนนรก
  • โครงสร้างที่ลึก: แผนผังการตัดสินใจสำหรับการนำ AI ไปใช้

คุณควรใช้ a Stackเป็นกลุ่มของคุณ (ต่างจากการใช้งานส่วนใหญ่ที่ใช้ a Queue) เหตุผลนี้เป็นเพราะStackถ้าคุณกลับวัตถุไปยังสระและสิ่งอื่นคว้ามันทันที; มันจะมีโอกาสสูงที่จะอยู่ในหน้าเพจที่ใช้งานอยู่หรือแม้กระทั่งในแคช CPU หากคุณโชคดี มันเร็วกว่านิดหน่อย นอกจากนี้ จำกัด ขนาดพูลของคุณทุกครั้ง (เพียงไม่สนใจ 'เช็คอิน' หากเกินขีด จำกัด ของคุณ)

หลีกเลี่ยงการสร้างรายการใหม่เพื่อล้างพวกเขา

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

ใช้โครงสร้างอาร์เรย์ (หรือรายการ) ที่เป็นไปได้

คุณจะได้รับประโยชน์เล็กน้อยจากการใช้งาน structs (หรือประเภทค่าโดยทั่วไป) ถ้าคุณผ่านมันไปมาระหว่างวัตถุ ตัวอย่างเช่นในระบบอนุภาค 'ดี' ส่วนใหญ่อนุภาคแต่ละอันจะถูกเก็บไว้ในอาเรย์ขนาดใหญ่: อาเรย์และและดัชนีถูกส่งผ่านไปรอบ ๆ แทนที่จะเป็นอนุภาค เหตุผลที่มันใช้งานได้ดีเพราะเมื่อ GC ต้องการรวบรวมอาร์เรย์มันสามารถข้ามเนื้อหาทั้งหมดได้ (มันเป็นอาร์เรย์ดั้งเดิม - ไม่มีอะไรให้ทำที่นี่) ดังนั้นแทนที่จะมองหาวัตถุ 10,000 ชิ้น GC ต้องการเพียงแค่ดู 1 อาร์เรย์: รับมาก! อีกครั้งนี้จะทำงานเฉพาะกับประเภทค่า

หลังจาก RoyT ให้ข้อเสนอแนะที่เป็นไปได้และสร้างสรรค์บางอย่างฉันรู้สึกว่าฉันจำเป็นต้องขยายเพิ่มเติม คุณควรใช้เทคนิคนี้เฉพาะเมื่อคุณจัดการกับเอนทิตีจำนวนมาก (หลายพันถึงหลายหมื่น) นอกจากนี้ยังต้องเป็นโครงสร้างที่ไม่ต้องมีฟิลด์ชนิดการอ้างอิงใด ๆ และต้องอาศัยอยู่ในอาร์เรย์อย่างชัดเจนพิมพ์ ตรงกันข้ามกับความคิดเห็นของเขาเรากำลังวางมันลงในอาเรย์ซึ่งน่าจะเป็นสนามในชั้นเรียน - ซึ่งหมายความว่ามันจะทำให้กองขึ้น (เราไม่ได้พยายามหลีกเลี่ยงการจัดสรรฮีป - เพียง แต่หลีกเลี่ยงการทำงานของ GC) เราใส่ใจจริงๆคือความจริงที่ว่ามันเป็นหน่วยความจำต่อเนื่องที่มีค่ามากมายที่ GC สามารถมองเห็นในการO(1)ดำเนินการแทนการO(n)ดำเนินการ

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

GC.Collect ()

นี่เป็นวิธีที่ดีที่สุดในการถ่ายภาพตัวเองด้วยเท้า (ดู: "การพิจารณาประสิทธิภาพ") ด้วย GC ทั่วไป คุณควรเรียกมันว่าเมื่อคุณได้สร้างEXTREMEปริมาณขยะ - และตัวอย่างหนึ่งที่ที่อาจจะมีปัญหาเป็นเพียงหลังจากที่คุณได้โหลดเนื้อหาสำหรับระดับ - และแม้แล้วคุณควรอาจจะเป็นเพียงการเก็บรวบรวมรุ่นแรก ( GC.Collect(0);) หวังว่าจะป้องกันการส่งเสริมวัตถุไปยังรุ่นที่สาม

IDisposable และ Nulling ภาคสนาม

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

นำตัวอย่างต่อไปนี้:

MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// G1 Collection
MyNestObject (G2) -> MyFurtherNestedObject (G2)
// G2 Collection
MyFurtherNestedObject (G3)

หากMyFurtherNestedObjectมีวัตถุหลายเมกะไบต์คุณสามารถรับประกันได้ว่า GC จะไม่มองมันนานนัก - เพราะคุณเลื่อนขั้นเป็น G3 โดยไม่ได้ตั้งใจ ตัดกันด้วยตัวอย่างนี้:

MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// Dispose
MyObject (G1)
MyNestedObject (G1)
MyFurtherNestedObject (G1)
// G1 Collection

รูปแบบ Disposer ช่วยให้คุณตั้งค่าวิธีที่คาดเดาได้ในการขอให้วัตถุล้างเขตข้อมูลส่วนตัวของพวกเขา ตัวอย่างเช่น:

public class MyClass : IDisposable
{
    private MyNestedType _nested;

    // A finalizer is only needed IF YOU CONTROL UNMANAGED RESOURCES
    // ~MyClass() { }

    public void Dispose()
    {
       _nested = null;
    }
}

1
WTF คุณเกี่ยวกับ? .NET Framework บน Windows เป็นรุ่นที่แน่นอน ชั้นเรียนของพวกเขามีวิธีการGetGeneration
DeadMG

2
.NET บน Windows ใช้ Generational Garbage Collector อย่างไรก็ตาม. NET Compact Framework ที่ใช้กับ WP7 และ Xbox360 มี GC จำกัด มากขึ้นซึ่งอาจเป็นมากกว่ารายการแบบเรียบไม่ใช่แบบทั่วไป
Roy T.

Jonathan Dickinson: ฉันต้องการเพิ่มว่าคำตอบนั้นไม่ได้เป็นคำตอบเสมอไป ใน. Net และอาจเป็นเช่นเดียวกันโมโนโครงสร้างไม่จำเป็นต้องอยู่บนสแต็ก (ซึ่งดี) แต่สามารถอยู่บนฮีป (ซึ่งเป็นที่ที่คุณต้องการ Garbage Collector) กฎสำหรับเรื่องนี้ค่อนข้างซับซ้อน แต่โดยทั่วไปจะเกิดขึ้นเมื่อโครงสร้างมีประเภทที่ไม่ใช่ค่า
Roy T.

1
@RoyT ดูความคิดเห็นด้านบน ฉันชี้ให้เห็นว่าโครงสร้างนั้นดีสำหรับข้อมูลซับจำนวนมากในอาเรย์ - เช่นระบบอนุภาค หากคุณกังวลเกี่ยวกับโครงสร้างของ GC ในอาเรย์เป็นวิธีที่จะไป สำหรับข้อมูลเช่นอนุภาค
Jonathan Dickinson

10
@DeadMG ยัง WTF ไม่ได้รับการเรียกร้องอย่างมากเนื่องจากคุณไม่ได้อ่านคำตอบอย่างถูกต้อง โปรดสงบอารมณ์ของคุณและอ่านคำตอบอีกครั้งก่อนที่จะเขียนความคิดเห็นแสดงความเกลียดชังในชุมชนที่ประพฤติตัวดีเช่นนี้
Jonathan Dickinson

7

การสร้างอินสแตนซ์น้อยมากเกิดขึ้นโดยอัตโนมัติภายในไลบรารีฐานเว้นแต่คุณจะเรียกสิ่งที่ต้องการ การจัดสรรหน่วยความจำส่วนใหญ่และการจัดสรรคืนมาจากรหัสของคุณเองและคุณสามารถควบคุมได้

ตามที่ฉันเข้าใจคุณไม่สามารถหลีกเลี่ยงสิ่งนี้ได้ทั้งหมด - สิ่งที่คุณทำได้คือให้แน่ใจว่าคุณรีไซเคิลและรวมออบเจ็กต์เมื่อเป็นไปได้ใช้ structs แทนคลาสหลีกเลี่ยงระบบ UnityGUI และโดยทั่วไปแล้วหลีกเลี่ยงการสร้างวัตถุใหม่ในหน่วยความจำแบบไดนามิก เมื่อประสิทธิภาพมีความสำคัญ

นอกจากนี้คุณยังสามารถบังคับให้มีการรวบรวมขยะในบางช่วงเวลาซึ่งอาจจะช่วยได้หรือไม่: ดูคำแนะนำที่นี่: http://unity3d.com/support/documentation/Manual/iphone-Optimizing-Scripts.html


2
คุณควรอธิบายความหมายของการบังคับให้ GC มันเล่นความเสียหายด้วยการวิเคราะห์พฤติกรรมและเลย์เอาต์ GC และสามารถนำไปสู่ปัญหาหน่วยความจำขนาดใหญ่ถ้าใช้ไม่ถูกต้อง: ชุมชน XNA / MVPs / พนักงานโดยทั่วไปพิจารณาโพสต์โหลดเนื้อหาในเวลาที่เหมาะสมเท่านั้นที่จะบังคับให้รวบรวม คุณควรหลีกเลี่ยงการกล่าวถึงมันโดยสิ้นเชิงหากคุณอุทิศเพียงหนึ่งประโยคให้กับเรื่องนั้น GC.Collect()= หน่วยความจำว่างตอนนี้ แต่มีปัญหาน่าจะเกิดขึ้นในภายหลัง
Jonathan Dickinson

ไม่คุณควรอธิบายความหมายของการบังคับให้ GC เพราะคุณรู้มากกว่าฉัน :) อย่างไรก็ตามโปรดทราบว่า Mono GC ไม่จำเป็นต้องเหมือนกับ Microsoft GC และความเสี่ยงอาจไม่เหมือนกัน
Kylotan

ถึงกระนั้น +1 ฉันเดินหน้าและอธิบายประสบการณ์ส่วนตัวของ GC - ซึ่งคุณอาจสนใจ
Jonathan Dickinson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.