หากต้องการเพิ่มประสิทธิภาพการใช้หน่วยความจำของเกมอย่างมีประสิทธิภาพ คุณต้องทำความเข้าใจก่อนว่าแพลตฟอร์ม Android วัดหน่วยความจำอย่างไร และวิธีใช้การวัดและส่งข้อมูลทางไกลของระบบ, Diagnostic API และเครื่องมือสร้างโปรไฟล์ คู่มือนี้จะอธิบายรายละเอียดเกี่ยวกับวิธีตรวจสอบ บันทึก และวิเคราะห์การจัดสรรหน่วยความจำของเกมตามหลักเกณฑ์ใหม่ของแพลตฟอร์ม
ทำความเข้าใจเมตริก RSS และ Swap
หากต้องการวิเคราะห์และแก้ไขข้อบกพร่องเกี่ยวกับลักษณะการทำงานของหน่วยความจำในเกมอย่างมีประสิทธิภาพ คุณต้องทำความเข้าใจเมตริกทางเทคนิคที่แพลตฟอร์ม Android ใช้สำหรับการบังคับใช้หน่วยความจำอย่างละเอียด ดูข้อมูลพื้นฐานโดยละเอียดเกี่ยวกับวิธีประมวลผลและตรวจสอบพารามิเตอร์การวัดและส่งข้อมูลทางไกลนี้ ในสภาพแวดล้อมจริงได้ที่ เอกสารประกอบ Android Vitals - การใช้งานหน่วยความจำ (RSS แบบไม่ระบุตัวตน + Swap)
1. RSS แบบไม่ระบุตัวตน (RssAnon)
ขนาดชุดหน่วยความจำที่ใช้งาน (Resident Set Size หรือ RSS) จะวัดส่วนของหน่วยความจำที่กระบวนการใช้และเก็บไว้ใน RAM จริงของอุปกรณ์ RSS แบ่งออกเป็นหน่วยความจำที่สำรองด้วยไฟล์และหน่วยความจำแบบไม่ระบุตัวตน เมตริกการบังคับใช้ของ Android จะเน้นที่ RSS แบบไม่ระบุตัวตนเท่านั้น
- สิ่งที่รวมอยู่: หน้าหน่วยความจำที่กระบวนการของเกมจัดสรรโดยตรง ซึ่งไม่ได้ลิงก์กับไฟล์จริงในพื้นที่เก็บข้อมูล หน้าเหล่านี้รวมถึงฮีป Java หรือ Kotlin, สแต็กการดำเนินการของเธรด และที่สำคัญคือการจัดสรรหน่วยความจำเนทีฟ (เช่น ตัวจัดสรรเครื่องมือ C++ ที่กำหนดเอง หรือบล็อกหน่วยความจำที่ขอโดยใช้ malloc หรือ new แบบเนทีฟ และมีการเปลี่ยนแปลงโดยตรรกะของเกม) ดูข้อมูลเพิ่มเติม เกี่ยวกับเมตริกนี้ได้ในพจนานุกรมหน่วยความจำของกระบวนการ (RSS)
- ความสำคัญ: เครื่องมือเกมใช้พูลหน่วยความจำเนทีฟขนาดใหญ่เพื่อจัดการ ฟิสิกส์ การแสดงผล และตรรกะ เนื่องจากพูลเหล่านี้ไม่ได้สำรองด้วยไฟล์ จึงอยู่ใน RSS แบบไม่ระบุตัวตนทั้งหมดและเป็นส่วนใหญ่ของการใช้หน่วยความจำจริงของเกม
2. Swap ที่ไม่ได้บีบอัด (VmSwap)
Android ไม่รองรับพื้นที่ Swap แบบดั้งเดิมที่ใช้ดิสก์เนื่องจากข้อจำกัดด้านการสึกหรอของพื้นที่เก็บข้อมูลแฟลชและความหน่วง แต่จะใช้ zRAM (Swap ที่ไม่ได้บีบอัด) แทน
- สิ่งที่รวมอยู่: เมื่อการใช้ RAM จริงเพิ่มขึ้น Daemon การจัดการหน่วยความจำของเคอร์เนลจะบีบอัดหน้าแบบไม่ระบุตัวตนที่ไม่ได้ใช้งานและย้ายไปยัง ส่วนที่ไม่ได้บีบอัดโดยเฉพาะของ RAM จริง (zRAM)
- การคำนวณเมตริก: ระบบจะติดตามข้อมูลนี้ตามขนาดที่ไม่ได้บีบอัด (VmSwap) เพื่อประเมินความต้องการหน่วยความจำจริงของเกม หากเกมจัดสรรหน่วยความจำและระบบ Swap หน่วยความจำนั้นไปยัง zRAM ระบบจะยังนับหน่วยความจำนั้นรวมอยู่ในการใช้หน่วยความจำทั้งหมดของเกม
3. สถานะของกระบวนการ
การใช้งานหน่วยความจำจะแบ่งตามสถานะของกระบวนการใน Android Vitals สำหรับนักพัฒนาเกม SDK หรือเกมของบุคคลที่สามอาจทริกเกอร์บริการที่ผู้ใช้รับรู้หรือบริการเบื้องหลังโดยไม่คาดคิด
- สิ่งที่รวมอยู่: เบื้องหน้า, บริการที่ผู้ใช้รับรู้, เบื้องหลัง และ แคช
- ความสำคัญ: สถานะของกระบวนการที่แตกต่างกันจะส่งผลต่อการจัดการหน่วยความจำของระบบปฏิบัติการ
Android ที่แตกต่างกัน คุณอาจไม่ทราบว่าเกมกำลังทำงานด้วยสถานะของกระบวนการที่ละเอียดอ่อนหาก SDK ของบุคคลที่สามทริกเกอร์งานเบื้องหลังโดยไม่ได้ตั้งใจ ตรวจสอบว่าเกมกำลังทำงานอยู่เบื้องหลังหรือไม่
โดยใช้
RunningAppProcessInfo
Application Programming Interface (API)
Android มี System API ที่ช่วยให้เกมตอบสนองต่อการใช้หน่วยความจำมากเกินไปแบบไดนามิกและบันทึกการวินิจฉัยหน่วยความจำโดยละเอียดในขณะรันไทม์
ตอบสนองต่อเหตุการณ์การลดหน่วยความจำ
ระบบใช้ onTrimMemory เพื่อแจ้งให้แอปทราบเกี่ยวกับเหตุการณ์วงจรชีวิตซึ่งเป็นโอกาสที่ดีที่แอปจะลดการใช้งานหน่วยความจำโดยสมัครใจ
และหลีกเลี่ยงไม่ให้ระบบหยุดแอปเนื่องจากหน่วยความจำไม่เพียงพอ (Low-Memory Killer หรือ LMK) เพื่อปล่อยหน่วยความจำให้
แอปอื่นๆ ใช้งาน
หากระบบหยุดแอปที่ทำงานอยู่เบื้องหลัง ผู้ใช้จะพบว่าการเริ่มแอปแบบ Cold Start ช้าเมื่อกลับมาใช้งานต่อ การลดการใช้งานหน่วยความจำเบื้องหลังจะช่วยป้องกันการหยุดแอปที่ทำงานอยู่เบื้องหลัง
เมื่อตอบสนองต่อเหตุการณ์การลดหน่วยความจำ ให้ปล่อยการจัดสรรหน่วยความจำขนาดใหญ่ที่สร้างใหม่ได้และไม่จำเป็นต้องใช้ในทันที
ตัวอย่าง: ลดหรือล้างบิตแมปที่แคชไว้ (ถอดรหัสจากพื้นที่เก็บข้อมูลในเครื่อง) เพื่อตอบสนองต่อ
TRIM_MEMORY_UI_HIDDEN
Kotlin
class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
override fun onTrimMemory(level: Int) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// Release memory related to UI elements, such as bitmap caches.
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Release memory related to background processing, such as by
// closing a database connection.
}
}
}
Java
public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
public void onTrimMemory(int level) {
switch (level) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// Release memory related to UI elements, such as bitmap caches.
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
// Release memory related to background processing, such as by
// closing a database connection.
}
}
}
}
ProfilingManager
เปิดตัวใน Android 15 (ระดับ API 35) ProfilingManager API ช่วยให้แอปพลิเคชันบันทึกสแนปช็อตที่กำหนดไว้แบบเป็นโปรแกรม (เช่น โปรไฟล์ฮีป, การติดตามระบบ และฮีปดัมป์ Java) ได้โดยตรงในขณะรันไทม์
นักพัฒนาแอปสามารถทริกเกอร์การบันทึกด้วยตนเองในฉากที่เฉพาะเจาะจง หรือลงทะเบียนทริกเกอร์อัตโนมัติ เช่น TRIGGER_TYPE_ANOMALY เพื่อทริกเกอร์การบันทึกโดยอัตโนมัติเมื่อกระบวนการของเกมละเมิดเกณฑ์ของตัวจำกัดหน่วยความจำ อย่างไรก็ตาม นักพัฒนาเกมต้องคำนึงถึงข้อจำกัดที่สำคัญในเครื่องมือเกมสมัยใหม่
หมายเหตุ: เครื่องมือเกมสมัยใหม่ (เช่น Unity หรือ Unreal) จะจัดการประสิทธิภาพการดำเนินการโดยการจัดสรรบล็อกหน่วยความจำเสมือนขนาดใหญ่ล่วงหน้าจากเคอร์เนลโดยใช้ mmap ที่มีแฟล็ก MAP_ANONYMOUS จากนั้นเครื่องมือจะใช้ตัวจัดสรรย่อยที่กำหนดเอง (เช่น ตัวจัดการหน่วยความจำเนทีฟของ Unity หรือ BinnedAllocators ของ Unreal) เพื่อแบ่งและจัดสรรบล็อกหน่วยความจำภายใน
ApplicationExitInfo
หากระบบหยุดเกมที่ทำงานอยู่เบื้องหลังหรือหยุดเกมเนื่องจากละเมิดขีดจำกัดหน่วยความจำของกระบวนการแต่ละรายการ กลไก Crash Dump แบบเนทีฟหรือ Java มาตรฐาน (เช่น Firebase Crashlytics) จะไม่ลงทะเบียนเหตุการณ์ นักพัฒนาแอปควรใช้ประโยชน์จาก
ApplicationExitInfo API เมื่อเริ่มเกมเพื่อค้นหาและบันทึกการหยุดเหล่านี้
แบบเป็นโปรแกรม
- การใช้งาน: เมื่อเริ่มต้น ให้เรียก
ActivityManager.getHistoricalProcessExitReasons()เพื่อดึงข้อมูลเหตุผลที่ออกจากระบบของเซสชันล่าสุด - เหตุผลหลักที่ออกจากระบบเนื่องจากหน่วยความจำ
REASON_LOW_MEMORY: บ่งชี้ว่าระบบหยุดกระบวนการเนื่องจากหน่วยความจำไม่เพียงพอ (Low Memory Killer หรือ LMK) การหยุดนี้เกิดขึ้นเมื่อการใช้หน่วยความจำมากเกินไปในอุปกรณ์สูงและระบบปฏิบัติการต้องเรียกคืน RAM เหตุผลที่ออกจากระบบนี้บ่งชี้ว่าการใช้หน่วยความจำเบื้องหลังของเกมมีขนาดใหญ่เกินไปจนไม่สามารถทำงานร่วมกับแอปพลิเคชันอื่นๆ ได้REASON_MEMORY_LIMITER(Android 17 (ระดับ API 37) ขึ้นไป): บ่งชี้ว่าระบบหยุดกระบวนการโดยเฉพาะเนื่องจากกระบวนการใช้หน่วยความจำเกินขีดจำกัดหน่วยความจำ cgroup (RssAnon + VmSwap) ที่ตัวจำกัดหน่วยความจำของแพลตฟอร์มกำหนด การหยุดนี้อาจเกิดขึ้นได้แม้ว่าอุปกรณ์จะมีหน่วยความจำจริงเหลืออยู่มาก ซึ่งแสดงถึงการละเมิดขีดจำกัดของกระบวนการแต่ละรายการโดยตรง
ใช้เครื่องมือที่พร้อมใช้งาน
ใช้เครื่องมือแพลตฟอร์มต่อไปนี้ระหว่างการพัฒนาและการประกันคุณภาพเพื่อวัดการใช้งานหน่วยความจำของเกมอย่างแม่นยำ
meminfo
เครื่องมือนี้จะรวบรวมสถิติหน่วยความจำเพื่อแสดงจำนวนหน่วยความจำ PSS ที่ จัดสรรและหมวดหมู่ที่ใช้
พิมพ์สถิติ meminfo ด้วยวิธีใดวิธีหนึ่งต่อไปนี้
- ใช้คำสั่ง
adb shell dumpsys meminfo package-name - ใช้การเรียก
MemoryInfoจาก Android Debug API
สถิติ PrivateDirty จะแสดงจำนวน RAM ภายในกระบวนการ
ที่ย้ายไปยังดิสก์ไม่ได้และไม่ได้แชร์กับกระบวนการอื่นๆ ระบบจะใช้จำนวนนี้ได้ส่วนใหญ่เมื่อหยุดกระบวนการนั้น
จุดติดตามหน่วยความจำ
จุดติดตามหน่วยความจำจะติดตามจำนวนหน่วยความจำ RSS ที่เกมใช้ การคำนวณการใช้งานหน่วยความจำ RSS เร็วกว่าการคำนวณการใช้งาน PSS มาก เนื่องจากคำนวณได้เร็วกว่า RSS จึงแสดงการเปลี่ยนแปลงขนาดหน่วยความจำได้ละเอียดกว่าเพื่อการวัดการใช้งานหน่วยความจำสูงสุดที่แม่นยำยิ่งขึ้น ดังนั้น คุณจึงสังเกตเห็นการใช้งานหน่วยความจำสูงสุดที่อาจทำให้หน่วยความจำของเกมเต็มได้ง่ายขึ้น
Perfetto
Perfetto เป็นชุดเครื่องมือสำหรับการรวบรวมข้อมูลประสิทธิภาพและหน่วยความจำ
ในอุปกรณ์ รวมถึงแสดงข้อมูลใน UI บนเว็บ เครื่องมือนี้รองรับการติดตามที่ยาวนานตามต้องการ คุณจึงดูการเปลี่ยนแปลง RSS เมื่อเวลาผ่านไปได้ นอกจากนี้ คุณยังเรียกใช้การค้นหา SQL ในข้อมูลที่เครื่องมือสร้างขึ้นเพื่อการประมวลผลแบบออฟไลน์ได้ด้วย เปิดใช้การติดตามแบบยาว
จากแอปการติดตามระบบ ตรวจสอบว่าได้เปิดใช้หมวดหมู่memory:Memory
สำหรับการติดตามแล้ว สำหรับการเพิ่มแท็กหน่วยความจำที่กำหนดเองในการพัฒนาและ
การทดสอบ คุณยังใช้ heapprofd API (เบต้า) ได้ด้วย
ตรวจสอบ RssAnon และ Swap ใน Perfetto
หากต้องการตรวจสอบหน่วยความจำแบบไม่ระบุตัวตนและผลกระทบของ zRAM Swap ในเกม ให้โหลดไฟล์การติดตาม ใน UI บนเว็บที่ ui.perfetto.dev แล้วทำตามเทคนิคการวิเคราะห์ ต่อไปนี้ ซึ่งออกแบบมาสำหรับการศึกษาเชิงลึกเกี่ยวกับหน่วยความจำ (ดูรายละเอียดเพิ่มเติมได้ที่ การศึกษาเชิงลึกเกี่ยวกับการวิเคราะห์หน่วยความจำ Perfetto)
1. การแสดงภาพตัวนับหน่วยความจำในไทม์ไลน์
- ค้นหากระบวนการ: ในรายการการนำทาง ให้ค้นหาชื่อแพ็กเกจหรือชื่อกระบวนการของเกม
- ขยายกลุ่มแทร็ก: คลิกแถวกระบวนการเพื่อขยายแทร็กเธรด แล้วค้นหากลุ่มย่อยที่ชื่อว่าหน่วยความจำ
- วิเคราะห์แทร็ก:
- mem.rss.anon (RSS แบบไม่ระบุตัวตน): กราฟเส้นนี้แสดง RAM จริงแบบเรียลไทม์ ที่พูลหน่วยความจำที่ไม่ได้จัดการของเกมใช้ ตรวจสอบไทม์ไลน์นี้ระหว่างการโหลดฉาก ป๊อปอัป UI หรือการเปลี่ยนผ่านของเกมเพลย์เพื่อตรวจสอบการใช้งานหน่วยความจำสูงสุด
- mem.swap (Swap ที่บีบอัดหรือ VmSwap): กราฟนี้จะพล็อต ขนาดของบล็อกหน่วยความจำก่อนบีบอัดที่ย้ายไปยัง zRAM กิจกรรม Swap สูงที่เกิดขึ้นพร้อมกับเกมเพลย์บ่งชี้ว่าเกมกำลังทำงานในอุปกรณ์ที่มีหน่วยความจำจำกัดและระบบกำลังบีบอัดชิ้นงานเบื้องหลังอย่างต่อเนื่อง
2. การเรียกใช้การค้นหา SQL (ตัวประมวลผลการติดตาม) หากต้องการวิเคราะห์แบบออฟไลน์โดยละเอียด คุณ สามารถเรียกใช้การค้นหา SQL ได้โดยตรงภายในคอนโซล UI ของ Perfetto หรือใช้ ไลบรารี Trace Processor Python แบบสแตนด์อโลนเพื่อคำนวณการใช้งานหน่วยความจำสูงสุดทางสถิติ
ค้นหาการจัดสรร RSS แบบไม่ระบุตัวตนสูงสุด
SELECT max(value) / 1024 / 1024 AS max_rss_anon_mb FROM counter JOIN counter_track ON counter.track_id = counter_track.id WHERE counter_track.name = 'mem.rss.anon' AND counter_track.upid IN ( SELECT upid FROM process WHERE name = 'your.game.package.name' );เชื่อมโยง RssAnon และ VmSwap ที่การประทับเวลาที่กำหนด
SELECT ts, track.name AS metric_type, value / 1024 / 1024 AS size_mb FROM counter JOIN counter_track track ON counter.track_id = track.id WHERE (track.name = 'mem.rss.anon' OR track.name = 'mem.swap') AND track.upid IN ( SELECT upid FROM process WHERE name = 'your.game.package.name' ) ORDER BY ts ASC;
ดูรายละเอียดเพิ่มเติมเกี่ยวกับการตรวจสอบไฟล์การติดตามโดยใช้ Android Studio ได้ที่ ตรวจสอบ การติดตามระบบ: หน่วยความจำของกระบวนการ (RSS) ดูรายละเอียดเกี่ยวกับการเขียนสคริปต์โปรไฟล์หน่วยความจำ ได้ที่ บันทึกการจัดสรรแบบเนทีฟ
heapprofd
heapprofd เป็นเครื่องมือติดตามหน่วยความจำซึ่งเป็นส่วนหนึ่งของ Perfetto เครื่องมือนี้ช่วยคุณค้นหาหน่วยความจำรั่วได้โดยแสดงตำแหน่งที่จัดสรรหน่วยความจำโดยใช้ malloc คุณสามารถเริ่ม heapprofd ได้โดยใช้สคริปต์ Python และเนื่องจากเครื่องมือนี้มีค่าใช้จ่ายต่ำ จึงไม่ส่งผลต่อประสิทธิภาพการทำงานเหมือนเครื่องมืออื่นๆ เช่น Malloc Debug
bugreport
bugreport เป็นเครื่องมือบันทึกเพื่อดูว่าเกมขัดข้องเนื่องจากหน่วยความจำเต็มหรือไม่ เอาต์พุตของเครื่องมือนี้มีรายละเอียดมากกว่าการใช้ logcat มาก เครื่องมือนี้มีประโยชน์สำหรับการแก้ไขข้อบกพร่องเกี่ยวกับหน่วยความจำ เนื่องจากจะแสดงว่าเกมขัดข้องเนื่องจากหน่วยความจำเต็มหรือระบบหยุดเกมเนื่องจากหน่วยความจำไม่เพียงพอ (LMK)
ดูข้อมูลเพิ่มเติมได้ที่ บันทึกและอ่านรายงานข้อบกพร่อง
เครื่องมือเกมเอนจิน
แม้ว่าบันทึกระดับแพลตฟอร์มและการวัดและส่งข้อมูลทางไกลของระบบจะมีความสำคัญอย่างยิ่งต่อการติดตามเกณฑ์และการปฏิบัติตามข้อกำหนดของระบบปฏิบัติการ แต่เครื่องมือเฉพาะของเครื่องมือเกมจะช่วยให้คุณระบุการจัดสรรกลับไปยังออบเจ็กต์ของเกม ลักษณะการทำงานของสคริปต์ และลำดับชั้นของฉากที่ใช้งานอยู่ได้โดยตรง
Unity
ในสภาพแวดล้อม Unity Engine คุณสามารถประมาณการใช้หน่วยความจำที่ใช้ RSS แบบไม่ระบุตัวตน + Swap ของ Android ได้อย่างแม่นยำในขณะรันไทม์ด้วยความน่าเชื่อถือสูง (โดยทั่วไปจะแสดงความแปรปรวนน้อยกว่า 10% เมื่อเทียบกับค่าระดับระบบปฏิบัติการจริง) โดยใช้เครื่องมือและคลาสการสร้างโปรไฟล์แบบเนทีฟของ Unity
ดูบทแนะนำแบบทีละขั้นตอนที่สมบูรณ์ ซึ่งรวมถึงกฎการกำหนดค่าและสคริปต์รันไทม์ ได้ที่วิธีตรวจสอบหน่วยความจำด้วยเครื่องมือ Unity
- Unity Profiler API: คุณสามารถประมาณการใช้หน่วยความจำที่ไม่ได้จัดการของเกมแบบเป็นโปรแกรมในขณะรันไทม์ได้โดยการค้นหาเมตริกหลักของเครื่องมือ ดังนี้
- การใช้คลาส Profiler: ติดตามการจัดสรรหน่วยความจำทั้งหมดโดยการรวม
ค่า
Profiler.GetTotalReservedMemoryLong()และProfiler.GetMonoHeapSizeLong() - การใช้คลาส
ProfilerRecorder: ตรวจสอบหมวดหมู่หน่วยความจำ แบบไดนามิก หากต้องการสร้างค่าประมาณพื้นฐานที่เชื่อถือได้ ให้ดึงข้อมูลหน่วยความจำที่สงวนไว้ทั้งหมด (ในบิลด์เวอร์ชันที่เผยแพร่) หรือลบหน่วยความจำที่สงวนไว้ของ Gfx ออกจากหน่วยความจำที่สงวนไว้ทั้งหมด (ในบิลด์เวอร์ชันที่พัฒนา) เพื่อนำคอมโพเนนต์หน่วยความจำกราฟิกที่สำรองด้วยไฟล์ออก
- การใช้คลาส Profiler: ติดตามการจัดสรรหน่วยความจำทั้งหมดโดยการรวม
ค่า
- Unity Memory Profiler: หากต้องการระบุและแก้ไขข้อบกพร่องเกี่ยวกับหน่วยความจำรั่วแบบออฟไลน์
ให้บันทึกสแนปช็อตหน่วยความจำและตรวจสอบแผนภูมิหน่วยความจำที่ใช้งานในอุปกรณ์
ที่อยู่ในส่วนหน่วยความจำทั้งหมด หากต้องการคำนวณการใช้หน่วยความจำโดยประมาณ ให้รวมค่าทั้งหมดของหมวดหมู่ต่อไปนี้ ไม่ได้ติดตาม, Android Runtime, เนทีฟ และมีการจัดการ
- ข้อจำกัดของ zRAM: ในสภาวะที่หน่วยความจำจำกัด เคอร์เนลของ Android จะบีบอัดหน้าหน่วยความจำที่ไม่ได้ใช้งานลงในพื้นที่ Swap (zRAM) เนื่องจาก Unity Memory Profiler ตรวจหาพารามิเตอร์ Swap ระดับระบบปฏิบัติการไม่ได้ คุณจึงอาจเห็นความคลาดเคลื่อนเล็กน้อยในการใช้หน่วยความจำในฉากที่ใช้หน่วยความจำมาก ตรวจสอบเพื่อเปรียบเทียบค่าประมาณกับ Perfetto เพื่อยืนยันค่าที่แน่นอน