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

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

  • ขีดจำกัดขั้นต่ำของการปกป้อง Daemon ของระบบ (SystemMemoryBudgetManager ที่มี memory.min): การจอง 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) เชลล์ที่แสดงต่อผู้ใช้จะยังคงได้รับการปกป้องชุดการทำงานอย่างเต็มรูปแบบ หาก Intent ของหน้าแรกเปลี่ยนเป็นแพ็กเกจ 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 และโค้ดที่แมปซึ่งมีการใช้งานบ่อย ขณะเดียวกันก็ปล่อยให้โค้ดที่ใช้งานน้อยที่สุด 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): ตั้งค่าเป็นขีดจำกัดขั้นต่ำของ Working Set 90% (ปรับขนาดแล้ว) ของกระบวนการ
  2. โหนด UID (uid_<uid>/memory.min): ตั้งค่าเป็นผลรวมที่แน่นอนของ Leaf Floor ที่มีการจัดการpid_* ดังนั้นกระบวนการที่ไม่มีการจัดการซึ่งแชร์ UID นั้น (เช่น AID_SYSTEM Daemon อื่นๆ หรือแพ็กเกจ 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_* ที่มีการจัดการในการรีเฟรชครั้งก่อน (เช่น ตัวเปิดหน้าแรกก่อนหน้านี้หลังจากที่ผู้ใช้เปลี่ยนตัวเปิด) เป็น 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 ครั้งเดียวไว้ที่หน้าต่างการอ่านล่วงหน้า 1 หน้าต่าง (โดยปกติคือ 128 KB ถึง 2 MB) แต่เมื่อ SystemMemoryBudgetManager.shouldSkipDefaultSystemPinning ทำงาน PinnerService จะแมปแต่ละไฟล์ใน config_defaultPinnerServiceFiles บนเทรดเบื้องหลังระหว่าง onStart, เติมไฟล์ลงในแคชของหน้าเว็บพร้อมกันด้วย Os.mlock และเรียกใช้ Os.munlock, munmap และ close ทันที ซึ่งจะช่วยรักษาเวลาในการเปิดเครื่องเร็วไว้โดยไม่เหลือไบต์ที่ล็อกไว้อย่างถาวร เพื่อให้สามารถนำหน้าเริ่มต้นใช้งาน Cold Startup ออกได้ในภายหลัง
  • การยกเว้นและการเรียกคืนแคชการสแกนแพ็กเกจการเปิดเครื่องของ system_server: ระหว่างการเปิดเครื่อง PackageManagerService จะสแกน APK ที่ติดตั้งไว้ โดยปล่อยให้แคชหน้าไฟล์ที่ไม่ได้แมปแบบ Cold ขนาด 1.5 ถึง 2.5 GB ถูกเรียกเก็บเงินจาก cgroup ของ system_server หากรวมอยู่ในการคำนวณ memory.min ของ system_server แคชการเปิดเครื่องใหม่นั้นจะใช้ขีดจำกัด RAM ทั่วโลก 10% จนเต็ม และลดระดับการป้องกันของ surfaceflinger, SystemUI และ audioserver ตามสัดส่วน เพื่อป้องกันไม่ให้เกิดปัญหา Cap Starvation SystemMemoryBudgetManager จะอ่าน memory.stat สำหรับ system_server, คำนวณแคชไฟล์ที่ไม่ได้แมปเป็น 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 ระดับกลางที่ว่างเปล่าและ cgroup ของ UID เผยแพร่การป้องกันไปยัง 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 ได้โดยใช้การวางซ้อนทรัพยากรเฟรมเวิร์กต่อไปนี้

  • 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 สำหรับการติดตามเชิงพื้นที่และกล้องด้วยการวางซ้อนต่อไปนี้

    <string-array translatable="false" name="config_systemMemoryBudgetAdditionalSystemProcesses">
        <item>android.hardware.perceptionhost</item>
        <item>auracamerahal</item>
    </string-array>
    
  • config_systemUi (สตริง): ชื่อแพ็กเกจของแอป SystemUI ของแพลตฟอร์ม ซึ่งแก้ไขด้วย MATCH_SYSTEM_ONLY

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

# Inspect policy state, resolved PIDs, and applied floors in dumpsys meminfo
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, บริการที่ทำงานอยู่เบื้องหน้าที่เชื่อมโยง หรือ Content Provider ที่มีการค้นหาบ่อยครั้ง หากไม่มีเพดานหน่วยความจำที่ชัดเจน กระบวนการเหล่านี้จะไม่ถูกจำกัดจนกว่าจะถึงขีดจำกัดตัวจำกัดหน่วยความจำหลายกิกะไบต์ (เช่น 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>

วิธีปรับเทียบงบประมาณอ้างอิงของ 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) จะกำหนดขีดจำกัดขั้นต่ำของ Working Set สำหรับงบประมาณ เพื่อไม่ให้กระบวนการมีความเสี่ยงที่จะใช้ ZRAM จนหมด ในขณะที่ช่องว่างขนาดใหญ่ระหว่าง memory.current P99 และ anon + swap P99 บ่งชี้ถึงแคชหน้าไฟล์ที่ memory.high สามารถเรียกคืนได้ หากแคชนั้นไม่ได้แมปและไม่มีการใช้งาน
  • พิจารณาหน่วยความจำที่ memory.high ไม่สามารถเรียกคืนได้: ไฟล์และหน่วยความจำที่ใช้ร่วมกันบางส่วนไม่ใช่แคชแบบ Cold หน้าที่ล็อกไว้ (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 หรือกิจกรรมสลับ

กราฟการเรียกคืนแคชแบบเย็น (pgsteal) และการทำงานแบบ Thrashing ของชุดการทำงาน (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) และการใช้ข้อมูลสูงสุดขณะใช้งาน P99 memory.current มากกว่า 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 ที่แชร์ ชุดการทำงานจะติดตามรุ่นของอุปกรณ์แทน 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 ไม่เปลี่ยนแปลง