งบประมาณหน่วยความจำของระบบ

ใน Android 17 ขึ้นไป แพลตฟอร์มจะควบคุมหน่วยความจำของกระบวนการของระบบโดยใช้กลไก cgroup v2 ของ Linux 2 กลไกที่เสริมกันซึ่งทำงานร่วมกับตัวจำกัดหน่วยความจำของแอปพลิเคชันทั่วไป ดังนี้

  • ขีดจำกัดขั้นต่ำของการปกป้อง Daemon ของระบบ (SystemMemoryBudgetManager ที่มี memory.min): เมื่อเปิดใช้โดยแฟล็กฟีเจอร์ com.android.server.am.system_process_memory_budgets (ปิดใช้โดยค่าเริ่มต้นใน Android 17 เวอร์ชันแรก) การจอง memory.min ของ cgroup v2 ที่ปรับแต่งเอง (ตั้งค่าเป็น 90% ของชุดการทำงานที่อยู่ในหน่วยความจำที่คงที่ของ Daemon แต่ละรายการ และจำกัดไว้ที่ 10% ของ RAM จริงทั้งหมด) จะปกป้อง Daemon แบบอินเทอร์แอกทีฟและกระบวนการ Shell ที่สำคัญ (system_server, surfaceflinger, HAL ของกราฟิก composer, audioserver, cameraserver, netd, SystemUI และ Launcher หน้าแรก) ภายใต้แรงกดดันด้านหน่วยความจำ โดยแทนที่การปักหมุดไฟล์ PinnerService mlock แบบคงที่
  • เพดานหน่วยความจำของแอประบบ (<memory-budget> ที่มี memory.high): ขีดจำกัดแบบซอฟต์ของ cgroup v2 memory.high ที่ปรับเทียบภาระงาน (โดยทั่วไปคือ 30 MB ถึง 190 MB) ซึ่งประกาศไว้ในไฟล์ Manifest สำหรับแอประบบแบบถาวรและแอปที่ทำงานอยู่เบื้องหลัง (Bluetooth, Nfc, CalendarProvider, Traceur, MediaProvider, :PhotoPicker, NetworkStack, AppSearch และ TeleService) จะเรียกคืนแคชหน้าเว็บไฟล์ที่ไม่ได้ใช้งานและฮีปแบบไม่ระบุชื่อหลังการทำงานหนักหน่วงในเชิงรุกก่อนที่จะเกิดแรงกดดันด้านหน่วยความจำทั่วโลก

ตารางต่อไปนี้เปรียบเทียบกลไกทั้ง 2 แบบ

มิติข้อมูล ขีดจำกัดขั้นต่ำของการป้องกันกระบวนการเบื้องหลังของระบบ เพดานหน่วยความจำของแอประบบ
บริการแพลตฟอร์ม SystemMemoryBudgetManager MemoryLimiter
การควบคุม Cgroup v2 พื้นราคาขั้นต่ำแบบเข้มงวด memory.min รายการ เพดานการเรียกคืนแบบซอฟต์ memory.high
กระบวนการเป้าหมาย ดีมอนแบบอินเทอร์แอกทีฟและ UI ของเชลล์ที่สำคัญต่อเวลาในการตอบสนอง (system_server, surfaceflinger, composer HAL, audioserver, cameraserver, netd, SystemUI และแอป Home) แอประบบแบบถาวรและแบบไม่มีส่วนหัว, Content Provider และบริการที่ทำงานอยู่เบื้องหลัง (เช่น Bluetooth, Nfc, MediaProvider, NetworkStack, AppSearch และ TeleService)
โมเดลการกำหนดขนาด การปรับแต่งด้วยตนเองขณะรันไทม์ (90% ของชุดการทำงานที่เสร็จสมบูรณ์ โดยจำกัดไว้ที่ 10% ของ RAM ทั้งหมดทั่วโลก) โดยไม่มีตารางการปรับแต่งต่ออุปกรณ์ แอตทริบิวต์ XML ของไฟล์ Manifest <memory-budget> แบบประกาศที่ปรับเทียบตามระดับ RAM, สถานะกระบวนการ และจำนวนพิกเซลที่แสดงผล
ประโยชน์หลัก ป้องกันการกระตุกของ UI แบบอินเทอร์แอกทีฟและการหยุดชะงักของการปลุกระบบภายใต้แรงกดดันของหน่วยความจำขณะแทนที่รายการไฟล์ PinnerService mlock แบบคงที่ ล้างแคชไฟล์แบบใช้ครั้งเดียวที่ไม่ได้ใช้งานและฮีปหลังการประมวลผลแบบเบิร์สต์อย่างต่อเนื่องเพื่อให้เหลือ RAM จริงมากขึ้นสำหรับแอปที่ทำงานอยู่เบื้องหน้าและแอปที่แคชไว้

ดูข้อมูลเพิ่มเติมเกี่ยวกับไวยากรณ์ <memory-budget>, แอตทริบิวต์ XML, API รันไทม์ และการบันทึกการใช้งานหน่วยความจำของระบบปฏิบัติการได้ที่ตั้งค่างบประมาณหน่วยความจำของแอปและทำความเข้าใจและแก้ปัญหาหน่วยความจำของ Android

ปกป้อง Daemon แบบอินเทอร์แอกทีฟที่สำคัญด้วย SystemMemoryBudgetManager

SystemMemoryBudgetManager จะเก็บกระบวนการที่อยู่บนเส้นทางการโต้ตอบของผู้ใช้โดยตรงไว้ใน RAM จริงในระหว่างที่หน่วยความจำมีแรงกดดัน แทนที่จะปักหมุดรายการไฟล์ APK, ODEX และ VDEX แบบคงที่ลงในหน่วยความจำด้วย mlock (เหมือนที่ PinnerService ทำกับ config_defaultPinnerServiceFiles และ config_pinnerHomePinBytes) SystemMemoryBudgetManager จะกำหนดขีดจำกัด memory.min ของ cgroup v2 แบบไดนามิกให้กับกระบวนการต่อไปนี้

  • ส่วนของระบบหลัก (/sys/fs/cgroup/system): system_server, surfaceflinger, HAL กราฟิก composer, audioserver, cameraserver และ netd รวมถึงระบบเนทีฟหรือ Daemon ของผู้ให้บริการที่เฉพาะเจาะจงสำหรับรูปแบบของอุปกรณ์ซึ่งแสดงอยู่ใน config_systemMemoryBudgetAdditionalSystemProcesses (เช่น android.hardware.perceptionhost และ auracamerahal ในอุปกรณ์ XR)
  • ส่วนของแอปแบบอินเทอร์แอกทีฟ (/sys/fs/cgroup/apps): กระบวนการหลักของแพ็กเกจ SystemUI ที่กำหนดค่าไว้ (config_systemUi) และกระบวนการหลักของผู้ถือบทบาทหน้าแรกของผู้ใช้ปัจจุบัน

การปกป้องผู้ถือบทบาท Home ควบคู่ไปกับ config_systemUi จะช่วยให้มั่นใจได้ว่ามีการครอบคลุมในอุปกรณ์ทุกรูปแบบ ในรูปแบบของอุปกรณ์ที่ config_systemUi เป็นแพ็กเกจความเข้ากันได้ที่มีขนาดเล็ก (เช่น Wear OS ที่หน้าปัดนาฬิกา การแจ้งเตือน และการ์ดทำงานภายในแอป Home) เชลล์ที่แสดงต่อผู้ใช้จะยังคงได้รับการปกป้องชุดการทำงานอย่างเต็มรูปแบบ หากความตั้งใจของหน้าแรกเปลี่ยนเป็นแพ็กเกจ config_systemUi กระบวนการจะได้รับการปกป้อง 1 ครั้ง เฉพาะกระบวนการหลักของแต่ละแพ็กเกจ (processName == packageName) เท่านั้นที่จะได้รับเกณฑ์การปกป้อง ดังนั้นกระบวนการย่อยเสริม (เช่น :screenshot) จะไม่ใช้โควต้าการปกป้อง

นโยบายการปรับแต่งด้วยตนเองและการมอบสิทธิ์ cgroup 3 ระดับ

เนื่องจากชุดการทำงานของ Daemon แบบอินเทอร์แอกทีฟจะแตกต่างกันไปตามรูปแบบของอุปกรณ์และขนาด RAM (เช่น SystemUI ใช้ประมาณ 45 MB ในสมาร์ทวอทช์, 60 MB ในโทรศัพท์ขนาด 6 GB และ 180 MB ในโทรศัพท์ขนาด 16 GB) ตารางตรวจสอบแบบคงที่ต่ออุปกรณ์จึงไม่เสถียร แต่ SystemMemoryBudgetManager จะดึงค่าพื้นโดยตรงจากฟุตพริ้นท์ที่อยู่ในหน่วยความจำของแต่ละกระบวนการโดยใช้ค่าคงที่ 3 ค่า ดังนี้

พารามิเตอร์นโยบาย ค่าเริ่มต้น ลักษณะการทำงาน
ราคาพื้นต่อกระบวนการ 90% ของ memory.current ที่ชำระแล้ว ลบด้วยแคชไฟล์ที่ไม่ได้แมป ปกป้องโค้ดที่แมปแบบไม่ระบุตัวตน, Slab และโค้ดที่แมปแบบ Hot ขณะที่ปล่อยให้ส่วนท้ายที่เย็นที่สุด 10% สามารถเรียกคืนได้ เพื่อให้ยังคงขับหน้าเว็บที่เย็นจริงๆ ออกไปได้ ระบบจะลบแคชไฟล์ที่ไม่ได้แมป (max(0, file - shmem - file_mapped) จาก memory.stat) ออกก่อนที่จะใช้อัตราส่วน 90% เพื่อไม่ให้แคชการสแกนแพ็กเกจการเปิดเครื่องหรือการอ่านเนื้อหาแบบครั้งเดียวทำให้ขีดจำกัดต่ำสุดเพิ่มขึ้น
ขีดจำกัด RAM ทั่วโลก 10% ของ RAM ทั้งหมด (config_systemMemoryBudgetLowKb) ตรงกับขอบเขต PinnerService config_pinnerMaxPinnedMemoryPercentage หากผลรวมของราคาพื้น 90% เกิน 10% ของ MemTotal ระบบจะลดราคาพื้นทุกรายการตามสัดส่วนด้วยปัจจัยเดียวกัน
ความถี่ในการรีเฟรช 1, 4, 16 และ 64 วินาทีหลังจากเริ่ม จากนั้นทุกๆ 30 นาที ชุดการจัดระเบียบทางเรขาคณิตจะรวมเข้ากับชุดการทำงานหลังการเริ่มต้นระบบของ Daemon แต่ละรายการโดยไม่ต้องฮาร์ดโค้ดความล่าช้าในการอุ่นเครื่องต่ออุปกรณ์ และการรีเฟรชสถานะคงที่ 30 นาทีจะติดตามการเติบโตและการหดตัวของชุดการทำงานทีละน้อยตลอดอายุการใช้งานของอุปกรณ์

ในตัวควบคุมหน่วยความจำ Linux cgroup v2 (mm/memcontrol.c:effective_protection) การป้องกัน memory.min ที่มีผลของ Leaf cgroup จะถูกจำกัดโดย memory.min ที่กำหนดค่าไว้ใน cgroup ระดับบนและระดับบรรพบุรุษ การตั้งค่า memory.min เฉพาะในไดเรกทอรี pid_<pid> ระดับล่างสุดขณะที่ uid_<uid>/memory.min เป็น 0 จะส่งผลให้ไม่มีการป้องกันที่มีประสิทธิภาพ ในทางกลับกัน การตั้งค่า memory.min แบบคงที่เฉพาะในส่วนบนสุด/sys/fs/cgroup/systemจะกระจายการปกป้องที่ไม่มีการอ้างสิทธิ์ไปยัง UID และกระบวนการย่อยทั้งหมดตามสัดส่วน (memory_recursiveprot) ซึ่งช่วยให้กระบวนการวินิจฉัยหรือกระบวนการรองที่แชร์ uid_1000 (android.uid.system) สามารถดึงการปกป้องออกจาก surfaceflinger และ audioserver ได้

เพื่อให้การแยกแบบต่อ Daemon ที่แน่นอน SystemMemoryBudgetManager จะเขียนการป้องกันในทั้ง 3 ระดับของลำดับชั้น cgroup v2 ในการรีเฟรชทุกครั้ง ดังที่แสดงในรูปที่ 1

  1. โหนดกระบวนการ Leaf (pid_<pid>/memory.min): ตั้งค่าเป็นขีดจำกัดล่างของชุดการทำงาน 90% (ปรับขนาดแล้ว) ของกระบวนการ
  2. โหนด UID (uid_<uid>/memory.min): ตั้งค่าเป็นผลรวมที่แน่นอนของ Leaf Floor ที่มีการจัดการ pid_* ดังนั้นกระบวนการที่ไม่มีการจัดการซึ่งแชร์ UID นั้น (เช่น Daemon อื่นๆ AID_SYSTEM หรือแพ็กเกจ sharedUserId="android.uid.system") จะมี memory.min = 0 และไม่สามารถอ้างสิทธิ์การป้องกันได้
  3. ส่วนระดับบนสุด (/sys/fs/cgroup/system/memory.min และ /sys/fs/cgroup/apps/memory.min): ตั้งค่าเป็นผลรวมที่แน่นอนของส่วนย่อย uid_* รายการ ระบบจะล้างค่า memory.low ที่ล้าสมัยในโหนดที่มีการจัดการเป็น 0 และรีเซ็ตไดเรกทอรี uid_* ที่มีการจัดการในการรีเฟรชครั้งก่อน (เช่น Launcher หน้าแรกก่อนหน้านี้หลังจากที่ผู้ใช้เปลี่ยน Launcher) เป็น 0

ลำดับชั้นการป้องกัน memory.min ของ cgroup v2 แบบ 3 ระดับใน /sys/fs/cgroup/system และ /sys/fs/cgroup/apps ซึ่งแสดงพื้นกระบวนการ Leaf ที่รวมเป็นโหนด UID และโหนด Slice ขณะที่กระบวนการข้างเคียงที่ไม่มีการป้องกันยังคงอยู่ที่ 0

รูปที่ 1 การมอบสิทธิ์ระดับการป้องกัน 3 ระดับในส่วนต่างๆ ของ cgroup

เหตุใดจึงใช้ memory.min แทน memory.low ในเคอร์เนล MGLRU

ใน Multi-Generational LRU (MGLRU ซึ่งเป็นเครื่องมือเรียกคืนหน้าเว็บเริ่มต้นในเคอร์เนล Android 6.6 ขึ้นไป) memory.low และ memory.min จะทำงานแตกต่างกันใน mm/vmscan.c:shrink_one ดังนี้

  • memory.low (การเลื่อนแบบรอบเดียว): cgroup ที่มีการใช้งานต่ำกว่า memory.low จะถูกหมุนเวียนไปยังส่วนท้ายของ memcg LRU (MEMCG_LRU_TAIL) และข้ามไปสำหรับการเรียกคืนรอบเดียว ในการส่งครั้งถัดไปภายใต้แรงกดดันด้านหน่วยความจำที่ต่อเนื่อง เคอร์เนลจะเรียกคืน cgroup ทั้งหมดและบันทึกเหตุการณ์ MEMCG_LOW ในการทดสอบความเครียดของอุปกรณ์จริงด้วยหน่วยความจำแบบไม่ระบุตัวตนที่เพิ่มขึ้นเป็น 85% ของ RAM จริง memory.low ช่วยให้ SystemUI ในสมาร์ทวอทช์ลดลงจากประมาณ 45 MB เหลือประมาณ 5-10 MB และ audioserver ในอุปกรณ์แบบพับได้ขนาด 16 GB ลดลงจาก 15 MB เหลือ 2 MB เทียบกับระดับต่ำสุดที่ประมาณ 14 MB
  • memory.min (ขีดจำกัดการป้องกันแบบเข้มงวด): ระบบจะข้าม cgroup ที่มีการใช้งานต่ำกว่า memory.min ในการเรียกคืนทรัพยากรส่วนกลาง ดังที่แสดงในรูปที่ 2 ภายใต้ภาระงานที่มีแรงกดดัน RAM 85% เท่ากัน memory.min จะกำจัดเฉพาะส่วนท้าย 10% ที่ไม่ค่อยได้ใช้งานของ Daemon แต่ละรายการ และคง SystemUI, surfaceflinger และ audioserver ไว้ที่ระดับต่ำสุด 90% โดยไม่มีข้อผิดพลาดซ้ำแบบไม่ระบุชื่อและไม่มีการสิ้นสุดกระบวนการ lmkd เพิ่มขึ้น

memory.min มีการจองทางกายภาพที่เข้มงวดซึ่งตรงกับการป้องกันของการปักหมุด PinnerService mlock ที่มาแทนที่ โดยมีข้อดีคือ LRU ของเคอร์เนล (แทนที่จะเป็นรายการไฟล์แบบคงที่) จะเลือกหน้าโค้ดที่ใช้งานบ่อย ฮีป และกราฟิกที่จะคงอยู่

การเปรียบเทียบหน่วยความจำที่ใช้โดย Daemon เมื่อการใช้งานหน่วยความจำของระบบเพิ่มขึ้นจาก 40% เป็น 95% ของ RAM จริง ซึ่งแสดงให้เห็นว่า memory.min คงที่อยู่ที่ระดับต่ำสุด 90% ขณะที่ memory.low ลดลงหลังจากผ่านการเลื่อน MGLRU เพียงครั้งเดียว

รูปที่ 2 การเก็บรักษาชุดการทำงานของ Daemon ภายใต้แรงกดดันด้านหน่วยความจำที่เพิ่มขึ้น

การเตรียมพร้อมก่อนเปิดเครื่องและการเรียกคืนแคชไฟล์ที่ไม่ได้แมปหลังเปิดเครื่อง

การแทนที่ PinnerService ถาวร mlock ของ config_defaultPinnerServiceFiles ต้องประสานงานพฤติกรรมเวลาเปิดเครื่อง 2 อย่าง ได้แก่

  • การเตรียมพร้อมการเปิดเครื่องในเบื้องหลังแบบครั้งเดียว (PinnerService): ข้าม PinnerService mlock โดยไม่เตรียมพร้อมแคชของหน้าจะเปลี่ยนการอ่านดิสก์ SystemUI APK, ODEX และ VDEX ขนาดประมาณ 95 MB ไปยังเส้นทาง การเปิดเครื่องแบบซิงโครนัสเมื่อ SystemUI เริ่มทำงาน (OnBootPhase_550) ซึ่งจะเพิ่มเวลา 300 ถึง 450 มิลลิวินาที ในการเปิดเครื่องจนเสร็จสมบูรณ์ การเรียก madvise(MADV_WILLNEED) ไม่เพียงพอเนื่องจาก force_page_cache_ra ในเคอร์เนลจะจำกัดการเรียก MADV_WILLNEED ครั้งเดียวไว้ที่หน้าต่าง Read-Ahead 1 หน้าต่าง (โดยปกติคือ 128 KB ถึง 2 MB) แต่เมื่อ SystemMemoryBudgetManager.shouldSkipDefaultSystemPinning ทำงานอยู่ PinnerService จะแมปแต่ละไฟล์ใน config_defaultPinnerServiceFiles บนเทรดเบื้องหลังระหว่าง onStart เติมไฟล์ลงในแคชของหน้าเว็บพร้อมกันด้วย Os.mlock และเรียกใช้ Os.munlock, munmap และ close ทันที ซึ่งจะช่วยรักษาเวลาในการเปิดเครื่องเร็วไว้โดยไม่เหลือไบต์ที่ล็อกไว้อย่างถาวร เพื่อให้สามารถนำหน้าเริ่มต้นใช้งาน Cold Startup ออกได้ในภายหลัง
  • การยกเว้นแคชไฟล์ที่ไม่ได้แมปและการเรียกคืนแคชการสแกนแพ็กเกจการเปิดเครื่อง: ระหว่างการเปิดเครื่อง PackageManagerService จะสแกน APK ที่ติดตั้งไว้ โดยปล่อยให้แคชหน้าไฟล์ที่ไม่ได้แมปแบบ Cold ขนาด 1.5 ถึง 2.5 GB ถูกเรียกเก็บเงินจาก cgroup system_server และกระบวนการอื่นๆ ที่ได้รับการปกป้อง (เช่น SystemUI การอ่านไฟล์เนื้อหา 3 มิติในอุปกรณ์ XR) ก็สามารถเก็บแคชไฟล์ที่ไม่ได้แมปจากการอ่านครั้งเดียวได้เช่นกัน หากรวมไว้ในการคำนวณ memory.min แคชที่ไม่มีข้อมูลนั้นอาจทำให้ RAM ทั่วโลกถึงขีดจำกัด 10% และลดทรัพยากรการป้องกันของ surfaceflinger, SystemUI และ audioserver ตามสัดส่วน เพื่อป้องกันไม่ให้เกิดการขาดแคลนขีดจำกัด SystemMemoryBudgetManager จะอ่าน memory.stat สำหรับแต่ละกระบวนการที่ได้รับการปกป้อง คำนวณแคชไฟล์ที่ไม่ได้แมปเป็น max(0, file - shmem - file_mapped) และลบแคชที่ไม่ได้แมปนั้นออกจาก memory.current ก่อนที่จะคำนวณขีดจำกัดขั้นต่ำ 90% ดังที่แสดงในรูปที่ 3 นอกจากนี้ 10 วินาทีหลังจาก PHASE_BOOT_COMPLETED ตัวจัดการจะเขียน <unmapped_file> swappiness=0 แบบครั้งเดียวไปยังโหนด memory.reclaim ของ system_server ใน Linux 6.2 และเคอร์เนลที่ใหม่กว่าเพื่อล้างแคชการสแกนแพ็กเกจที่ไม่ได้ใช้งานอย่างต่อเนื่องโดยไม่ต้องสลับฮีปที่ไม่ระบุชื่อหรือล้าง DEX และหน้าโค้ดที่แมปไว้ ในเคอร์เนลเวอร์ชันเก่า (เช่น android14-6.1) ที่ปฏิเสธ swappiness=0 ด้วย EINVAL ตัวจัดการจะข้ามการเขียน memory.reclaim แทนที่จะเปลี่ยนกลับไปใช้การเขียนเพื่อเรียกคืนที่ไม่มีคุณสมบัติเหมาะสมซึ่งจะสลับฮีปที่ไม่ระบุตัวตนออกระหว่างการเปิดเครื่อง

ไทม์ไลน์ของหน่วยความจำ system_server หลังจากการเปิดเครื่องแสดงการรีเฟรชแบบเรขาคณิตที่ 1, 4, 16 และ 64 วินาที การยกเว้นแคชการสแกนแพ็กเกจการเปิดเครื่องที่ไม่ได้แมปจากพื้น memory.min 90% และ memory.reclaim แบบครั้งเดียวที่มี swappiness=0

รูปที่ 3 การรวมขีดจำกัดการป้องกันและการเรียกคืนแคชหลังจากการเปิดเครื่อง

ข้อกำหนดของเคอร์เนลและการวางซ้อนการกำหนดค่าอุปกรณ์

SystemMemoryBudgetManager อาศัย mem_cgroup_calculate_protection ในเคอร์เนล Linux เพื่อเผยแพร่ memory.min ใน cgroup ที่ซ้อนกัน เคอร์เนล Android ทั่วไป android15-6.6 และ android16-6.12 มีการรองรับการป้องกันที่ซ้อนกันของ MGLRU แบบเต็มรูปแบบตั้งแต่แกะกล่อง อุปกรณ์ที่ใช้ android14-6.1 โดยเปิดใช้ MGLRU ต้องใช้ Backport อัปสตรีม 3 รายการใน mm/vmscan.c และ mm/memcontrol.c เพื่อให้ Slice ระดับกลางที่ว่างเปล่าและ UID cgroup เผยแพร่การป้องกันไปยัง cgroup ของกระบวนการ Leaf ดังนี้

  • 30d77b7eef01 (mm/mglru: fix ineffective protection calculation)
  • 8aa420617918 (mm/mglru: respect min_ttl_ms with memcgs)
  • adb8213014b2 (mm: memcg: fix stale protection of reclaim target memcg)

OEM สามารถเปิดใช้ ปรับแต่ง หรือปิดใช้ SystemMemoryBudgetManager ได้โดยใช้แฟล็กฟีเจอร์และการวางซ้อนทรัพยากรเฟรมเวิร์กต่อไปนี้

  • com.android.server.am.system_process_memory_budgets (แฟล็กฟีเจอร์ aconfig): เปิดใช้ SystemMemoryBudgetManager และข้ามการปักหมุดระบบ PinnerService และหน้าแรก mlock เริ่มต้น (ปิดใช้โดยค่าเริ่มต้นใน Android 17 รุ่นแรก)
  • config_systemMemoryBudgetLowKb (จำนวนเต็ม, KB): 0 (ค่าเริ่มต้น) จำกัดผลรวมของ memory.min ทั้งหมดไว้ที่ 10% ของ RAM จริงทั้งหมด ค่าบวกจะกำหนดขีดจำกัดที่ชัดเจนเป็น KB และค่าลบจะปิดใช้ SystemMemoryBudgetManager ในอุปกรณ์ (คงการปักหมุด mlock แบบคงที่ของ PinnerService เดิมไว้)
  • config_systemMemoryBudgetHighKb (จำนวนเต็ม, KB): 0 (ค่าเริ่มต้น) จะคง /sys/fs/cgroup/system/memory.high ไว้ที่ max ค่าบวกจะกำหนดเพดานส่วนแบ่งรายได้สูงสุด memory.high (ต้องมากกว่าหรือเท่ากับเพดานขั้นต่ำ)
  • config_systemMemoryBudgetAdditionalSystemProcesses (อาร์เรย์สตริง): แสดงรายการชื่อกระบวนการเบื้องหลังของระบบหรือผู้ให้บริการแบบเนทีฟที่สำคัญต่อเวลาในการตอบสนองเพิ่มเติมใน /sys/fs/cgroup/system (จับคู่กับ /proc/<pid>/comm หรือชื่อฐานของ argv[0] ใน /proc/<pid>/cmdline สำหรับ UID ของระบบและผู้ให้บริการภายใต้ 10000) เพื่อปกป้องด้วยระดับต่ำสุด 90% ของ memory.min ควบคู่ไปกับกระบวนการเบื้องหลังของระบบเริ่มต้น 6 รายการ ใช้การวางซ้อนนี้แทนการตั้งค่า memory.min ใน init.rc เนื่องจาก SystemMemoryBudgetManager จะคำนวณ uid_<uid>/memory.min ใหม่เป็นผลรวมของกระบวนการ Leaf ที่มีการจัดการ และล้างโหนด uid_* ที่ไม่มีการจัดการทุกครั้งที่รีเฟรช ตัวอย่างเช่น อุปกรณ์ XR สามารถปกป้อง Daemon ของ HAL สำหรับการติดตามเชิงพื้นที่และกล้อง (android.hardware.perceptionhost และ auracamerahal) และอุปกรณ์เดสก์ท็อป Android สามารถปกป้อง Daemon ของ HAL สำหรับเสียงและกล้อง USB (dras.core.audio และ android.hardware.camera.provider-usb-service.android-desktop) ได้

    <!-- XR device overlay -->
    <string-array translatable="false" name="config_systemMemoryBudgetAdditionalSystemProcesses">
        <item>android.hardware.perceptionhost</item>
        <item>auracamerahal</item>
    </string-array>
    
    <!-- Android Desktop device overlay -->
    <string-array translatable="false" name="config_systemMemoryBudgetAdditionalSystemProcesses">
        <item>dras.core.audio</item>
        <item>android.hardware.camera.provider-usb-service.android-desktop</item>
    </string-array>
    
  • config_systemUi (สตริง): ชื่อแพ็กเกจของแอป SystemUI ของแพลตฟอร์ม ซึ่งแก้ไขด้วย MATCH_SYSTEM_ONLY

หากต้องการตรวจสอบชั้น SystemMemoryBudgetManager และโหนด cgroup ที่ใช้งานอยู่ในอุปกรณ์ ให้เรียกใช้คำสั่งต่อไปนี้

# Inspect policy state, resolved PIDs, and applied floors
adb shell dumpsys system_server_dumper --name SystemMemoryBudgetManager
adb shell dumpsys meminfo | sed -n '/System Memory Budgets/,$p'
# Inspect raw cgroup v2 memory.min nodes across the system and apps slices
adb shell head -n 1 \
  /sys/fs/cgroup/system/memory.min \
  /sys/fs/cgroup/system/uid_*/memory.min \
  /sys/fs/cgroup/system/uid_*/pid_*/memory.min \
  /sys/fs/cgroup/apps/memory.min \
  /sys/fs/cgroup/apps/uid_*/memory.min \
  /sys/fs/cgroup/apps/uid_*/pid_*/memory.min

เชื่อมโยงแอปของระบบที่ทำงานอยู่ตลอดเวลาและทำงานอยู่เบื้องหลังกับงบประมาณหน่วยความจำ

แม้ว่า SystemMemoryBudgetManager จะปกป้อง Daemon แบบอินเทอร์แอกทีฟจากการเรียกคืนโดยใช้ขีดจำกัด memory.min แต่แอปอื่นๆ ของระบบและบริการแพลตฟอร์มที่มีสิทธิ์จำนวนมากก็ทำงานในเบื้องหลังเป็นเวลานานในฐานะกระบวนการ PERSISTENT, บริการที่ทำงานอยู่เบื้องหน้าที่เชื่อมโยง หรือผู้ให้บริการเนื้อหาที่ถูกค้นหาบ่อย หากไม่มีเพดานหน่วยความจำที่ชัดเจน กระบวนการเหล่านี้จะไม่ถูกจำกัดจนกว่าจะถึงขีดจำกัดตัวจำกัดหน่วยความจำหลายกิกะไบต์ (เช่น 4 GB ถึง 10 GB สำหรับแอปที่มองเห็นได้ และ 2 GB ถึง 5 GB สำหรับแอปที่ทำงานอยู่เบื้องหลัง) หรือแรงกดดันหน่วยความจำทั่วโลกจะทริกเกอร์กิจกรรม kswapd ทั่วทั้งระบบและตัวปิดแอปเมื่อหน่วยความจำเหลือน้อย (lmkd)

หากไม่มีข้อจำกัด กระบวนการของระบบที่ทำงานอยู่เบื้องหลังมักจะสะสมหน่วยความจำแบบ Cold จำนวนมากซึ่งไม่ได้เป็นส่วนหนึ่งของชุดการทำงานที่ใช้งานอยู่

  • แคชหน้าไฟล์ที่ไม่มีขอบเขต: บริการที่จัดทำดัชนีสื่อ ค้นหาฐานข้อมูล หรือซิงค์ปฏิทินและตารางผู้ให้บริการ (เช่น AppSearch, MediaProvider และ CalendarProvider) จะแมปหรือสตรีมผ่านไฟล์ขนาดใหญ่ในดิสก์ หากไม่มีเพดาน cgroup memory.high หน้าเว็บที่ได้รับการสนับสนุนไฟล์ที่สะอาดจากการอ่านครั้งเดียวจะยังคงถูกเรียกเก็บเงินจาก cgroup ของกระบวนการอย่างไม่มีกำหนด ซึ่งใช้ RAM ทางกายภาพหลายร้อยเมกะไบต์
  • การเก็บรักษาฮีปแบบไม่ระบุตัวตนหลังการทำงานแบบเบิร์ส: บริการที่พบกิจกรรมเพิ่มขึ้นอย่างรวดเร็วชั่วคราว เช่น Bluetooth การทำงานแบบเบิร์สของการค้นหาเสียงและ BLE, NetworkStack การตรวจสอบความถูกต้องของ TLS ของแคพทีฟพอร์ทัล หรือตัวช่วย UI ที่เปลี่ยนไปทำงานในเบื้องหลัง สามารถเก็บหน้าฮีปแบบไม่ระบุตัวตนที่ไม่มีการใช้งานไว้ได้นานหลังจากที่การทำงานแบบเบิร์สเสร็จสมบูรณ์

การใช้การประกาศ <memory-budget> ที่ปรับเทียบแล้ว (ขีดจำกัดแบบซอฟต์ memory.high โดยทั่วไปคือ 30 MB ถึง 190 MB) กับแอประบบมีประโยชน์หลัก 2 ประการดังนี้

  • การเรียกคืนหน้าเว็บที่แปลเป็นภาษาท้องถิ่นในเชิงรุก: เมื่อกระบวนการของระบบเกินงบประมาณ memory.high ที่กำหนดค่าไว้ เคอร์เนล Linux จะกำหนดให้เรียกคืนหน้าเว็บโดยเฉพาะที่ cgroup นั้น โดยจะลบหน้าเว็บที่สำรองข้อมูลไฟล์แบบ Cold ออกก่อน และบีบอัดหน้าเว็บที่ไม่ระบุตัวตนที่ไม่ได้ใช้งานไปยัง ZRAM โดยไม่ต้องรอแรงกดดันด้านหน่วยความจำของอุปกรณ์ทั่วโลก
  • RAM ที่พร้อมใช้งานมากขึ้นสำหรับแอปที่ทำงานอยู่เบื้องหน้าและพูลแอปที่แคชไว้: การจำกัดแคชไฟล์ที่ไม่ได้ใช้งานในบริการของระบบที่เปิดตลอดเวลาจะช่วยเรียกคืน RAM จริงหลายสิบถึงหลายร้อยเมกะไบต์ในอุปกรณ์ ซึ่งจะช่วยลดอาการกระตุกของ UI, ลดแรงกดดันในการเปิดกล้อง และทำให้แอปของผู้ใช้ทำงานต่อไปได้มากขึ้นในสถานะแคช

กำหนดค่างบประมาณหน่วยความจำของแอประบบ

แอประบบใช้ไวยากรณ์ไฟล์ Manifest <memory-budget>, การเรียงซ้อนสถานะกระบวนการ, การปรับขนาดความหนาแน่นของการแสดงผล และกฎการแก้ไขส่วนคำสั่งที่ตรงกันล่าสุดเหมือนกับแอปทั่วไป ดูรายการแอตทริบิวต์ทั้งหมดได้ที่ข้อมูลอ้างอิงแอตทริบิวต์ XML

OEM สามารถประกาศองค์ประกอบ <memory-budget> ภายใน AndroidManifest.xml ของแอประบบได้โดยตรง หรือรวมองค์ประกอบเหล่านั้นจากไฟล์ XML แยกต่างหากโดยใช้พร็อพเพอร์ตี้ additional_manifests ใน Android.bp ตามธรรมเนียมแล้ว AOSP จะจัดเก็บงบประมาณของแอประบบอ้างอิงไว้ในไฟล์ *-budget.xml แบบสแตนด์อโลน (ใน frameworks/base/core/memory_budgets/ สำหรับแพ็กเกจแพลตฟอร์มหลักหรือควบคู่ไปกับโมดูลแต่ละรายการ เช่น packages/modules/NetworkStack/memory_budgets/, packages/modules/AppSearch/apk/ และ packages/services/Telephony/) และแนบไว้กับ additional_manifests ดังนี้

android_app {
    name: "TeleService",
    manifest: "AndroidManifest.xml",
    additional_manifests: ["Telephony-budget.xml"],
    // ...
}

การกำหนดขอบเขตระดับแพ็กเกจ (UID) เทียบกับระดับกระบวนการ (PID)

การประกาศงบประมาณพื้นฐานระดับแพ็กเกจ (<application>) หรืองบประมาณแบบหลายกระบวนการ (<processes><process>) จะกำหนดว่าเคอร์เนลจะบังคับใช้ memory.high ที่ระดับ cgroup ของ UID (/sys/fs/cgroup/uid_<uid>/memory.high) หรือที่ระดับ cgroup ต่อ PID (/sys/fs/cgroup/uid_<uid>/pid_<pid>/memory.high) ดังที่แสดงในรูปที่ 4

  • งบประมาณระดับแพ็กเกจ (<application>): ใช้เมื่อ APK เรียกใช้กระบวนการเดียวภายใต้ UID เฉพาะของตนเอง (เช่น Bluetooth, Nfc, NetworkStack, CalendarProvider, Traceur หรือ AppSearch)
  • งบประมาณระดับกระบวนการ (<processes><process>): ใช้เมื่อ APK แชร์ android:sharedUserId (เช่น android.uid.phone ที่มี UID 1001 หรือ android.uid.system ที่มี UID 1000) กับแพ็กเกจระบบอื่นๆ เพื่อไม่ให้กระบวนการอื่นๆ ที่แชร์ UID นั้นถูกจำกัดร่วมกัน หรือเมื่อ APK เดียวเรียกใช้หลายกระบวนการที่มีบทบาทหน่วยความจำที่แตกต่างกัน (เช่น Daemon MediaProvider ในเบื้องหลังและกระบวนการย่อยของ UI :PhotoPicker)

ลำดับชั้น Cgroup v2 ที่เปรียบเทียบการบังคับใช้ memory.high ระดับ UID สำหรับ UID เฉพาะกับการบังคับใช้ memory.high ต่อ PID สำหรับ UID ของระบบที่แชร์และ APK แบบหลายกระบวนการ

รูปที่ 4 เพดานหน่วยความจำระดับ UID เทียบกับระดับ PID

ตัวอย่างต่อไปนี้แสดง MediaProvider-budget.xml ซึ่งกำหนดขอบเขตงบประมาณที่แบ่งตามระดับ RAM ให้กับกระบวนการ MediaProvider เริ่มต้น (<process>) และงบประมาณที่ปรับขนาดตามการแสดงผลให้กับกระบวนการ UI :PhotoPicker แอตทริบิวต์ android:maxDeviceRamMb แต่ละรายการจะจับคู่กับอุปกรณ์ที่มี RAM จริงทั้งหมด (MemTotal) เท่ากับหรือต่ำกว่าเกณฑ์ MB ที่ระบุ (หรือใช้กับความจุ RAM ใดก็ได้เมื่อละเว้น) ระบบจะจัดเรียงส่วนคำสั่งจากค่าเริ่มต้นที่ไม่มีข้อจำกัดไปจนถึงเกณฑ์ maxDeviceRamMb ที่เล็กที่สุด เพื่อให้กฎการแก้ไขส่วนคำสั่งที่ตรงกันล่าสุดเลือกระดับที่เข้มงวดที่สุดที่เกี่ยวข้อง ดังนี้

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.android.providers.media.module">
    <uses-sdk android:targetSdkVersion="10000" />
    <application>
        <processes>
            <process>
                <memory-budget
                    android:maxMb="85" />
                <memory-budget
                    android:maxMb="75"
                    android:maxDeviceRamMb="12288" />
                <memory-budget
                    android:maxMb="60"
                    android:maxDeviceRamMb="8192" />
            </process>
            <process android:process=":PhotoPicker">
                <memory-budget
                    android:maxMb="85"
                    android:additionalBytesPerDisplayPixel="25" />
            </process>
        </processes>
    </application>
</manifest>

เมื่อประกาศ <processes> ในไฟล์ Manifest ของงบประมาณ ให้แสดงรายการ <process> เฉพาะสำหรับกระบวนการที่ประกาศ <memory-budget> (หากกระบวนการเริ่มต้นของแอปประกาศชื่อ android:process ที่กำหนดเองใน <application> ให้ระบุชื่อนั้นในองค์ประกอบ <process android:process="..."> แทนการใช้แท็ก <process /> ที่ไม่มีชื่อ) ในระหว่างการคอมไพล์ APK aapt2 จะเพิ่มรายการ <process> โดยอัตโนมัติสำหรับกระบวนการที่ไม่มีงบประมาณซึ่งประกาศโดยคอมโพเนนต์ในไฟล์ Manifest ที่ผสาน และเมื่อจำเป็นต้องใช้เพื่อความเข้ากันได้กับ Android 17 จะแทรก <memory-budget android:maxMb="1000000" /> ที่ล้อมรอบไว้ใต้ <application> เพื่อให้งบประมาณต่อกระบวนการแยกออกจากกระบวนการเป้าหมาย

วิธีปรับเทียบงบประมาณอ้างอิงของ AOSP ในอุปกรณ์ Google Pixel

งบประมาณหน่วยความจำของแอประบบอ้างอิงใน AOSP ได้รับการปรับเทียบในอุปกรณ์ Google Pixel ที่มี RAM จริงตั้งแต่ 6 GB ถึง 16 GB (รวมถึงโทรศัพท์ อุปกรณ์แบบพับได้ และแท็บเล็ตขนาด 6 GB, 8 GB, 12 GB และ 16 GB) และความละเอียดในการแสดงผลตั้งแต่ ~1.2 MP (720×1600) ถึง ~4.5 MP (2076×2152)

การปรับเทียบจะรวมแหล่งข้อมูลเสริม 2 แหล่งเข้าด้วยกัน ได้แก่ การวัดและส่งข้อมูลทางไกลภาคสนามของ cgroup v2 ทั่วทั้งกลุ่ม และการกวาดmemory.highอุปกรณ์จริงที่มีการควบคุมในเส้นทางของผู้ใช้ที่สำคัญ (CUJ)

การวิเคราะห์การตรวจวัดระยะไกลของ cgroup v2 ทั่วทั้งกลุ่มยานพาหนะ

ระบบจะรวบรวมสแนปชอต cgroup v2 เป็นระยะๆ ในกลุ่มอุปกรณ์และแบ่งตามระดับ RAM จริง (≤8 GB, 12 GB และ ≥16 GB) และบัケットสถานะกระบวนการ (foreground, perceptible, background และ cached) โดยใช้หลักการ 6 ข้อในการวิเคราะห์ทางสถิติ ดังนี้

  • แยกย่อย memory.current ออกเป็นชุดการทำงานเทียบกับแคชไฟล์ที่เรียกคืนได้: เนื่องจาก memory.high ควบคุมการเรียกเก็บเงิน cgroup ที่ประจำอยู่ (memory.current) สแนปชอตแต่ละรายการจึงถูกแยกย่อยโดยใช้ memory.stat ออกเป็นฮีปที่ไม่ระบุชื่อ (anon), หน้าที่ไม่ระบุชื่อที่สลับ (swap) และแคชหน้าเว็บที่สำรองข้อมูลไฟล์ (file) ดังที่แสดงในรูปที่ 5 ความต้องการแบบไม่ระบุตัวตนทั้งหมดที่บีบอัดไม่ได้ (anon + swap ที่ P99) จะกำหนดขีดจำกัดขั้นต่ำของชุดการทำงานสำหรับงบประมาณ เพื่อให้กระบวนการไม่เสี่ยงต่อการใช้ ZRAM จนหมด ในขณะที่ช่องว่างขนาดใหญ่ระหว่าง memory.current P99 และ anon + swap P99 บ่งชี้ถึงแคชหน้าไฟล์ที่ memory.high สามารถเรียกคืนได้ หากแคชนั้นไม่ได้แมปและไม่มีการใช้งาน
  • พิจารณาหน่วยความจำที่ memory.high ไม่สามารถเรียกคืนได้: ไฟล์และหน่วยความจำที่ใช้ร่วมกันบางส่วนไม่ใช่แคชที่ไม่มีการใช้งาน หน้าที่ล็อกไว้ (unevictable ใน memory.stat เช่น หน่วยความจำที่ใช้ร่วมกันซึ่งล็อกไว้ด้วย mlock หรือ RAM ของผู้มาเยือนที่ล็อกไว้ของเครื่องเสมือนซึ่งกระบวนการโฮสต์ถูกเรียกเก็บเงินจาก cgroup ของแอป) จะกำหนดขีดจำกัดขั้นต่ำที่เข้มงวดภายใต้งบประมาณ และหน้าที่แมป (file_mapped) มักจะเป็นส่วนหนึ่งของชุดการทำงานที่ใช้งานอยู่ ตรวจสอบ unevictable, shmem และ file_mapped ก่อนที่จะถือว่าหน่วยความจำไฟล์เป็นแบบเรียกคืนได้ ตัวอย่างเช่น หน่วยความจำ AppSearch ประมาณ 100 MB ถูกล็อกและไม่สามารถขับออกได้ ดังนั้นงบประมาณจึงครอบคลุมพื้นที่ดังกล่าวรวมถึงชุดการทำงานของโฮสต์ที่ใช้งานอยู่ โดยมีพื้นที่ว่างสำหรับพื้นที่ที่ล็อกไว้เพื่อขยาย
  • กรองการลดประสิทธิภาพในสถานะไม่มีการใช้งาน: Content Provider (CalendarProvider) และเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์แบบออนดีมานด์ (Traceur) ใช้เวลา 70% ถึง 99.9% ของสแนปชอตในฟิลด์ที่ไม่มีการใช้งานที่ประมาณ 1 MB ถึง 3 MB การคำนวณเปอร์เซ็นไทล์ดิบในสแนปชอตทั้งหมดจะทำให้ค่าประมาณ P95 และ P99 ลดลงอย่างมาก แต่จะคำนวณเปอร์เซ็นไทล์และช่วงความเชื่อมั่นจากสแนปชอตที่ใช้งานอยู่ซึ่งไม่มีการเจือจาง (กรองสแนปชอต CACHED และสแนปชอตพื้นฐานที่ไม่มีการใช้งานออก)
  • ใช้ช่วงความเชื่อมั่นที่จัดกลุ่มตามอุปกรณ์: เนื่องจากอุปกรณ์เครื่องเดียวจะให้ภาพรวมเป็นระยะๆ หลายครั้งเมื่อเวลาผ่านไป การถือว่าภาพรวมเป็นตัวอย่างอิสระจะทำให้ความไม่แน่นอนของส่วนท้ายต่ำเกินไป ค่าประมาณชุดการทำงาน P95 และ P99 ทั้งหมดได้รับการประเมินโดยใช้ช่วงความเชื่อมั่น 95% แบบคลัสเตอร์อุปกรณ์แบบอนุรักษ์นิยม (โดยใช้จำนวนอุปกรณ์ที่ไม่ซ้ำกัน N_dev เป็นขนาดตัวอย่างที่มีประสิทธิภาพ)
  • พิจารณาการเพิ่มขึ้นอย่างรวดเร็วชั่วคราวด้วย memory.peak: การสุ่มตัวอย่างเป็นระยะอาจพลาดการเพิ่มขึ้นอย่างรวดเร็วที่มีการจัดสรรสูงและมีอายุสั้น (เช่น การตรวจสอบความถูกต้อง TLS ของแคพทีฟพอร์ทัล NetworkStack และการตรวจสอบ DNS พร้อมกัน หรือการเริ่มต้น Bluetooth A2DP และ BLE) สำหรับบริการที่มีการใช้งานแบบเป็นช่วงๆ งบประมาณจะได้รับการปรับขนาดให้ครอบคลุมเปอร์เซ็นไทล์ที่ 90 ถึง 95 ของระดับสูงสุดของ memory.peak ของเคอร์เนลตลอดอายุการใช้งานในระดับ RAM ต่างๆ
  • ยืนยันว่าระดับ RAM แสดงถึง RAM ไม่ใช่รุ่นของอุปกรณ์: อุปกรณ์รุ่นใหม่ๆ มักจะมีทั้ง RAM และฟีเจอร์มากกว่า ดังนั้นชุดการทำงานที่เพิ่มขึ้นตามระดับ RAM อาจเพิ่มขึ้นตามฟีเจอร์จริงๆ ก่อนเพิ่มระดับ maxDeviceRamMb ให้แบ่งข้อมูลตามรุ่นอุปกรณ์ ตัวอย่างเช่น com.android.phone มีค่า P99 anon + swap เท่ากัน (~85 ถึง 90 MB) ในโทรศัพท์ 12 GB และ 16 GB ของรุ่นเดียวกัน ขณะที่โทรศัพท์แบบพับได้ 12 GB รุ่นเก่าใช้ 40 MB ดังนั้นงบประมาณจึงใช้ค่าเดียวโดยไม่มีระดับ RAM เพื่อให้อุปกรณ์ที่มี RAM ต่ำกว่าซึ่งมาพร้อมฟีเจอร์โทรศัพท์ที่ใหม่กว่ามีงบประมาณเพียงพอ

การแยกย่อยหน่วยความจำ cgroup เมื่อเวลาผ่านไปเป็นชุดการทำงานแบบไม่ระบุตัวตนที่บีบอัดไม่ได้ หน้าต่างแคชไฟล์ที่ใช้งานอยู่ และแคชไฟล์แบบใช้ครั้งเดียวที่ไม่ได้ใช้งานซึ่งเรียกคืนเกิน memory.high

รูปที่ 5 ชุดการทำงานเทียบกับแคชไฟล์ที่เรียกคืนได้

CUJ ของอุปกรณ์จริงและการกวาดการปรับขนาดการแสดงผล

เพื่อตรวจสอบงบประมาณที่ได้จากการวัดและส่งข้อมูลทางไกล และปรับเทียบงบประมาณตามสัดส่วนการแสดงผล (เช่น :PhotoPicker) และงบประมาณเบื้องหลังเฉพาะสถานะ (เช่น Traceur) ระบบได้ดำเนินการกวาด memory.high แบบเรียลไทม์ในอุปกรณ์จริงที่มี RAM ตั้งแต่ 6 GB ถึง 16 GB ดังนี้

  • การค้นหาจุดหักเหที่ไม่มีการเรียกใช้ซ้ำ: ขณะขับเคลื่อน CUJ แบบต้นทางถึงปลายทาง (เช่น การเลื่อนดูตารางกริดรูปภาพอย่างรวดเร็ว การเรียกดูอัลบั้ม การปัดดูตัวอย่างรูปภาพและวิดีโอความละเอียดสูงแบบเต็มหน้าจอ และการบันทึกและจัดเก็บการติดตาม) memory.high จะลดลงขณะวัดตัวนับ cgroup memory.stat (workingset_refault_anon, workingset_refault_file, pgscan และ pgsteal) และ memory.events (high) ดังที่แสดงในรูปที่ 6 บัดเจ็ตที่เหมาะสมจะอยู่เหนือจุดหักเหที่ไม่มีการเรียกใช้ซ้ำเล็กน้อย จุดหักเหที่ไม่มีการเกิดข้อผิดพลาดซ้ำคือเกณฑ์ที่เคอร์เนลจะเรียกคืนแคชไฟล์แบบใช้ครั้งเดียวที่ไม่มีการใช้งาน (pgsteal > 0) โดยไม่ทำให้เกิดการ Thrashing ของชุดการทำงานที่ใช้งานอยู่ (workingset_refault == 0)
  • การตรวจสอบการปรับขนาดพิกเซลการแสดงผลในความละเอียดต่างๆ: การทดสอบ :PhotoPicker ในความละเอียดการแสดงผลจริงและจำลอง 4 แบบตั้งแต่ ~1.2 MP (720×1600) ถึง ~4.5 MP (จอแสดงผลด้านในที่กางออก 2076×2152) ยืนยันว่าชุดการทำงานบิตแมป Glide และ ViewPager2 ที่ใช้งานอยู่ปรับขนาดตามจำนวนพิกเซลการแสดงผลแบบเชิงเส้น นอกจากนี้ เกณฑ์พื้นฐานที่ไม่ใช่จอแสดงผลยังแตกต่างกันไปตามอุปกรณ์ โดยที่จำนวนพิกเซลเกือบเท่ากัน จุดหักเหที่ไม่มีการเกิดข้อผิดพลาดของหน่วยความจำอยู่ที่ประมาณ 140 MB บนจอแสดงผลด้านนอกของอุปกรณ์แบบพับได้ขนาด 16 GB เทียบกับ 110 ถึง 125 MB บนโทรศัพท์ขนาด 6 GB เกณฑ์พื้นฐาน 85 MB บวก 25 ไบต์ต่อพิกเซลการแสดงผล (android:maxMb="85" และ android:additionalBytesPerDisplayPixel="25") จะยังคงสูงกว่าจุดเปลี่ยนที่วัดได้ในจอแสดงผลที่ทดสอบทุกจอ (~110 MB ใน HD+, ~145 MB ใน FHD+ และ ~190 MB ในจอแสดงผลแบบกางออก)
  • การยืนยันการเก็บขยะ ART ในสถานะเบื้องหลัง: ใน cgroup v2 หน้า boot.art ที่แชร์โดย Zygote และหน้าอิมเมจเฟรมเวิร์กจะยังคงถูกเรียกเก็บเงินจาก cgroup ของ Zygote และถือว่าเป็นพื้นที่ที่ได้รับการยกเว้นในระหว่างการเก็บขยะแบบ Mark-Compact (CMC) พร้อมกันของ ART การทดสอบความเครียดช่วยยืนยันว่าเมื่อแอป UI ของระบบเปลี่ยนไปใช้ background การจำกัด memory.high (เช่น เป็น 30 MB ใน Traceur) จะจำกัดฮีปส่วนตัวและแคชหน้าเว็บได้อย่างสมบูรณ์โดยไม่ทำให้เกิดการทำลาย GC หรือกิจกรรมการสลับ

กราฟการเรียกคืนแคชแบบ Cold (pgsteal) และการทำงานที่ไม่มีประสิทธิภาพของชุดการทำงาน (workingset_refault) เนื่องจาก memory.high ลดลงในระหว่าง CUJ ซึ่งไฮไลต์จุดหักเหที่ไม่มีข้อผิดพลาดและงบประมาณเป้าหมาย

รูปที่ 6 การค้นหาจุดที่ไม่มีการเกิดข้อผิดพลาดซ้ำในระหว่าง CUJ

สรุปงบประมาณของแอประบบอ้างอิง AOSP

ตารางต่อไปนี้สรุปงบประมาณอ้างอิงที่ปรับเทียบแล้วใน AOSP ในระดับ RAM และสถานะกระบวนการต่างๆ

แพ็กเกจหรือกระบวนการของระบบ ขอบเขต RAM ≤8 GB RAM 12 GB RAM ≥16 GB ไดรเวอร์การปรับเทียบหลัก
com.android.bluetooth UID (<application>) 80 MB 95 MB 110 MB ครอบคลุมการใช้ข้อมูลสูงสุดตลอดอายุการใช้งาน P90 memory.peak (~75 ถึง 110 MB) และการใช้งาน memory.current P99 มากกว่า 2 เท่า
com.android.nfc UID (<application>) 50 MB 60 MB 60 MB ครอบคลุม Host Card Emulation P99 ที่ใช้งานอยู่ (สูงสุดประมาณ 50 MB) และ P90 ถึง P95 memory.peak ขณะตัดแต่งแคชไฟล์ที่ไม่ได้ใช้งาน
com.android.providers.calendar UID (<application>) 70 MB เป็นค่าเริ่มต้น (เบื้องหน้า)
50 MB perceptible และ background
ชุดการทำงานที่ใช้งานอยู่จะไม่เปลี่ยนแปลงใน RAM ทุกระดับ งบประมาณเบื้องหลังจะเก็บพื้นที่ว่างที่ไม่ระบุชื่อไว้สำหรับการปลุกเพื่อช่วยเตือนความจำขณะตัดแต่งแคชหน้า SQLite ระหว่างการซิงค์
com.android.traceur UID (<application>) 85 MB โดยค่าเริ่มต้น (เบื้องหน้าและรับรู้ได้)
30 MB background
รักษาพื้นที่ว่าง 85 MB ไว้ทั้งหมดในระหว่างการบันทึกและส่งออกการติดตามที่ใช้งานอยู่ (TraceService) ขณะที่จำกัดสถานะไม่มีการใช้งานและการออกอากาศไว้ที่ 30 MB
com.android.providers.media.module (หลัก) PID (<process>) 60 MB 75 MB 85 MB ให้พื้นที่ว่างประมาณ 10 ถึง 30 MB เหนือ P99 anon + swap ขณะเรียกคืนแคชไฟล์ SQLite และภาพขนาดย่อที่ไม่ได้ใช้งาน
com.android.providers.media.module:PhotoPicker PID (<process>) 85 MB + 25 ไบต์ต่อพิกเซล
(~110 MB ใน HD+, ~145 MB ใน FHD+, ~190 MB ใน ~4.5 MP Fold)
ปรับขนาดตามสัดส่วนด้วยจำนวนพิกเซลที่แสดง (additionalBytesPerDisplayPixel="25") เพื่อให้ตรงกับจุดเปลี่ยนของชุดการทำงานบิตแมป Glide
com.android.networkstack UID (<application>) 65 MB 70 MB 85 MB หน่วยความจำแบบไม่ระบุตัวตนเป็นหลัก โดยมีขนาดเท่ากับอายุการใช้งาน P95 memory.peak เพื่อให้ครอบคลุมการทำงานแบบฉับพลันของ TLS, DNS และ DHCP ของแคพทีฟพอร์ทัล
com.android.appsearch.apk UID (<application>) 130 MB ครอบคลุมหน่วยความจำที่ล็อกไว้และไม่สามารถลบออกได้ประมาณ 100 MB บวกกับชุดการทำงานของโฮสต์ที่ใช้งานอยู่ประมาณ 5 MB โดยมีพื้นที่ว่างสำหรับขยายพื้นที่ที่ล็อกไว้ ไม่มีการเกิดข้อผิดพลาดซ้ำที่วัดได้ที่ ≥125 MB ขณะที่แคชหน้าเว็บแบบ Cold (สูงสุด ~330 MB ที่ P99) ได้รับการเรียกคืน
com.android.phone (TeleService) PID (<process>) 120 MB กำหนดขอบเขตต่อ PID ใน UID 1001 ที่แชร์ Working Set จะติดตามรุ่นของอุปกรณ์แทน RAM ดังนั้นงบประมาณเดียวจะสูงกว่า P99 anon + swap สูงสุดต่อโมเดลประมาณ 30 MB

วิธีที่ OEM สามารถปรับเทียบงบประมาณของแอประบบใหม่สำหรับอุปกรณ์ของตนเอง

OEM ที่ปรับแต่งแอประบบ (เช่น การเพิ่มโปรไฟล์บลูทูธของผู้ให้บริการ ส่วนขยายโทรศัพท์ที่กำหนดเอง หรือตัวจัดทำดัชนีสื่อของ OEM) หรือจัดส่งบริการของระบบที่ติดตั้งไว้ล่วงหน้าเพิ่มเติมสามารถปรับเทียบงบประมาณที่มีอยู่ใหม่หรือเขียนไฟล์ *-budget.xml ใหม่สำหรับพอร์ตโฟลิโอฮาร์ดแวร์ของตน

รวบรวมโปรไฟล์หน่วยความจำ cgroup v2 ที่ใช้งานอยู่

สำหรับกระบวนการของระบบเป้าหมายแต่ละกระบวนการ ให้อ่านโหนดหน่วยความจำ cgroup v2 ในส่วน /sys/fs/cgroup/uid_<uid>/pid_<pid>/ ในเวิร์กโหลดและระดับ RAM ที่เป็นตัวแทน ดังนี้

  • memory.current: หน่วยความจำ Resident ปัจจุบันที่เรียกเก็บเงินจาก cgroup (ไม่รวมหน้าเว็บที่แชร์โดย Zygote)
  • memory.peak: จุดสูงสุดตลอดอายุการใช้งาน memory.current จุดสูงสุดนับตั้งแต่เริ่มกระบวนการ
  • memory.swap.current: การใช้งานสว็อปที่บีบอัดใน ZRAM ปัจจุบัน
  • memory.stat: รายละเอียดของ anon, file, pgscan, pgsteal, workingset_refault_anon และ workingset_refault_file
  • memory.events: จำนวนสะสมของการละเมิด high (memory.high เหตุการณ์การควบคุมปริมาณและการเรียกคืน)

เมื่อรวบรวมข้อมูลการตรวจวัดระยะไกลจากอุปกรณ์ Dogfood หรืออุปกรณ์ภาคสนาม ให้แยกสถานะกระบวนการที่ทำงานอยู่ (TOP, FOREGROUND_SERVICE, การเชื่อมโยงที่ทำงานอยู่ และการซิงค์ที่ทำงานอยู่) ออกจากสถานะ CACHED ที่ไม่มีการใช้งานหรือสถานะ PERSISTENT ที่ไม่มีการใช้งาน เพื่อให้สแนปชอตที่ไม่มีการใช้งานไม่ทำให้การคำนวณชุดการทำงาน P95 และ P99 ของคุณลดลง

แยกฮีปที่บีบอัดไม่ได้ออกจากแคชไฟล์ที่เรียกคืนได้

เปรียบเทียบเปอร์เซ็นไทล์ที่ 99 ของ anon + swap กับเปอร์เซ็นไทล์ที่ 99 ของ memory.current ดังนี้

  • บริการที่ใช้แคชไฟล์เป็นหลัก (file &gg; anon เช่น ผู้ให้บริการฐานข้อมูล, ตัวจัดทำดัชนีของ Search และ Daemon บันทึก): ตั้งค่า android:maxMb เพื่อให้มีพื้นที่ว่างที่เหมาะสม (โดยทั่วไปคือ 1.5 เท่าถึง 4 เท่า) เหนือ P99 anon + swap บวกกับหน้าต่างการทำงานสำหรับหน้าไฟล์ที่ใช้งานบ่อย การกำหนดค่านี้ช่วยให้เคอร์เนลขับหน้าไฟล์ที่ไม่ได้แมปซึ่งไม่มีการใช้งานออกอย่างต่อเนื่องโดยไม่ต้องแตะฮีปที่ไม่ระบุชื่อ หาก unevictable หรือ file_mapped มีขนาดใหญ่ (เช่นเดียวกับหน่วยความจำที่ล็อกหรือฐานข้อมูลที่แมปหน่วยความจำ) ให้นับพื้นที่ดังกล่าวเป็นชุดการทำงานด้วย
  • บริการที่ใช้ฮีปแบบไม่ระบุชื่อเป็นหลักหรือบริการที่มีการใช้งานเพิ่มขึ้นอย่างรวดเร็ว (anon เป็นส่วนใหญ่ของ memory.current เช่น สแต็กเครือข่ายหรือการเชื่อมต่อ): ปรับขนาด android:maxMb ให้ครอบคลุม P90 ถึง P95 ของ memory.peak เพื่อไม่ให้การใช้งานที่เพิ่มขึ้นอย่างรวดเร็วชั่วคราวทริกเกอร์การสลับ ZRAM แบบซิงโครนัสในเทรดที่ไวต่อเวลาในการตอบสนอง
  • กระบวนการ UI ที่ใช้การแสดงผลอย่างหนัก (เช่น เครื่องมือเลือก แกลเลอรี หรือตัวช่วย UI ของระบบ): วัดพื้นที่ที่ไม่ใช่ UI พื้นฐานและพื้นที่บิตแมปที่เพิ่มขึ้นต่อเมกะพิกเซลในจอแสดงผลที่มีความละเอียดต่ำสุดและสูงสุด แล้วแสดงงบประมาณโดยใช้ android:maxMb บวก android:additionalBytesPerDisplayPixel

กวาด memory.high ในอุปกรณ์จริง

ก่อนที่จะกำหนดงบประมาณใหม่ให้กับบิลด์ ให้ใช้ am memory-limiter manual (หรือเขียนลงในโหนด cgroup memory.high ของกระบวนการโดยตรงในบิลด์ userdebug) เพื่อทดสอบขีดจำกัดที่เป็นไปได้ขณะเรียกใช้การทดสอบ CUJ อัตโนมัติ

# Override memory.high for a specific PID to 60 MB during a CUJ test
adb shell am memory-limiter manual <PID> 60MB

ตรวจสอบ memory.stat และ memory.events ก่อนและหลัง CUJ ดังนี้

  • งบประมาณต่ำเกินไป: workingset_refault_anon หรือ workingset_refault_file เพิ่มขึ้นอย่างรวดเร็วระหว่าง CUJ, memory.swap.current เพิ่มขึ้นอย่างต่อเนื่องระหว่างการทำงานที่ทำงานอยู่เบื้องหน้า หรือเฟรม CUJ หรือเวลาในการตอบสนองของ Binder ลดลง เพิ่มงบประมาณให้เกินจุดหักเหเริ่มต้น
  • งบประมาณสูงเกินไป: memory.events:high ยังคงเป็น 0 แม้ว่าจะมีการสตรีมไฟล์หรือภาระงานการจัดทำดัชนีจำนวนมาก และ file ใน memory.stat จะเพิ่มขึ้นอย่างไม่มีที่สิ้นสุดด้วยหน้าเว็บที่ไม่มีการเข้าชม จำกัดงบประมาณสำหรับชุดการทำงานที่ใช้งานอยู่
  • งบประมาณได้รับการปรับเทียบอย่างดี: memory.current ในสภาวะคงที่ยังคงต่ำกว่า memory.high ในระหว่างการทำงานปกติ ในระหว่างที่มีการใช้ไฟล์หรือแคชอย่างหนัก pgsteal จะเรียกคืนหน้าไฟล์ที่ไม่ได้ใช้งาน ขณะที่ workingset_refault_anon ยังคงใกล้เคียงกับศูนย์และเวลาในการตอบสนองของ CUJ ไม่เปลี่ยนแปลง