การระบุ Jank ที่เกี่ยวข้องกับ Jitter

กระวนกระวายใจคือพฤติกรรมของระบบแบบสุ่มที่ป้องกันไม่ให้งานที่รับรู้ทำงานได้ หน้านี้อธิบายวิธีการระบุและแก้ไขปัญหา jitter ที่เกี่ยวข้องกับ jitter

ความล่าช้าของตัวกำหนดเวลาเธรดของแอปพลิเคชัน

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

  • เธรดตัวช่วยเหลือแบบสุ่มในแอปอาจล่าช้าเป็นเวลาหลายมิลลิวินาทีโดยไม่มีปัญหา
  • เธรด UI ของแอปพลิเคชันอาจสามารถทนต่อการกระวนกระวายใจได้ 1-2 มิลลิวินาที
  • ไดรเวอร์ kthreads ที่ทำงานเป็น SCHED_FIFO อาจทำให้เกิดปัญหาหากรันได้ในราคา 500us ก่อนที่จะรัน

เวลาที่รันได้สามารถระบุได้ใน systrace โดยแถบสีน้ำเงินที่อยู่หน้าส่วนที่รันอยู่ของเธรด เวลาที่รันได้ยังสามารถถูกกำหนดโดยระยะเวลาระหว่างเหตุการณ์ sched_wakeup สำหรับเธรดและเหตุการณ์ sched_switch ที่ส่งสัญญาณการเริ่มต้นของการประมวลผลเธรด

กระทู้ที่ยาวเกินไป

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

  1. ใช้ cpusets ตามที่อธิบายไว้ใน การควบคุมปริมาณความร้อน
  2. เพิ่มค่า CONFIG_HZ
    • ในอดีต ค่าถูกกำหนดไว้ที่ 100 บนแพลตฟอร์ม arm และ arm64 อย่างไรก็ตาม นี่เป็นอุบัติเหตุในประวัติศาสตร์ และไม่คุ้มค่าที่จะใช้กับอุปกรณ์แบบโต้ตอบ CONFIG_HZ=100 หมายความว่าระยะเวลาหนึ่งจะมีความยาว 10 มิลลิวินาที ซึ่งหมายความว่าการปรับสมดุลโหลดระหว่าง CPU อาจใช้เวลา 20 มิลลิวินาที (สองช่วงเวลาสั้นๆ) จึงจะเกิดขึ้น สิ่งนี้สามารถส่งผลต่อ jank บนระบบที่โหลดได้อย่างมาก
    • อุปกรณ์ล่าสุด (Nexus 5X, Nexus 6P, Pixel และ Pixel XL) จัดส่งมาพร้อมกับ CONFIG_HZ=300 สิ่งนี้น่าจะมีค่าใช้จ่ายด้านพลังงานเล็กน้อยในขณะที่ปรับปรุงเวลาการทำงานได้อย่างมาก หากคุณเห็นว่าปัญหาด้านการใช้พลังงานหรือประสิทธิภาพเพิ่มขึ้นอย่างมีนัยสำคัญหลังจากเปลี่ยน CONFIG_HZ เป็นไปได้ว่าไดรเวอร์ตัวใดตัวหนึ่งของคุณกำลังใช้ตัวจับเวลาโดยอิงตามระยะเวลาจิฟฟี่ดิบ แทนที่จะเป็นมิลลิวินาทีและแปลงเป็นจิฟฟี่ โดยปกติแล้วจะแก้ไขได้ง่าย (ดู แพตช์ ที่แก้ไขปัญหาตัวจับเวลา kgsl บน Nexus 5X และ 6P เมื่อแปลงเป็น CONFIG_HZ=300)
    • สุดท้ายนี้ เราได้ทดลองใช้ CONFIG_HZ=1000 บน Nexus/Pixel และพบว่ามีประสิทธิภาพและพลังงานลดลงอย่างเห็นได้ชัด เนื่องจากโอเวอร์เฮดของ RCU ลดลง

ด้วยการเปลี่ยนแปลงทั้งสองเพียงอย่างเดียว อุปกรณ์ควรจะดูดีขึ้นมากสำหรับเวลารันเธรด UI ขณะโหลด

ใช้ sys.use_fifo_ui

คุณสามารถลองขับเคลื่อนเวลาที่รันเธรด UI ให้เป็นศูนย์ได้โดยการตั้งค่าคุณสมบัติ sys.use_fifo_ui เป็น 1

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

ด้วยเหตุนี้ ตัวกำหนดเวลา RT เริ่มต้นจะย้ายเธรด UI ที่ทำงานค่อนข้างยาวนานของคุณจากคอร์ขนาดใหญ่ที่มีความถี่สูงไปยังคอร์เล็ก ๆ ที่ความถี่ต่ำสุดอย่างมีความสุข หาก FIFO kthread ที่มีลำดับความสำคัญสูงกว่าเกิดขึ้นเพื่อปลุกการทำงานบนคอร์ขนาดใหญ่เดียวกัน ซึ่งจะทำให้เกิดการถดถอยของประสิทธิภาพที่สำคัญ เนื่องจากตัวเลือกนี้ยังไม่ได้ใช้กับอุปกรณ์ Android ที่จัดส่ง หากคุณต้องการใช้ โปรดติดต่อทีมประสิทธิภาพของ Android เพื่อช่วยคุณตรวจสอบ

เมื่อเปิดใช้งาน sys.use_fifo_ui ActivityManager จะติดตามเธรด UI และ RenderThread (สองเธรดที่มีความสำคัญต่อ UI มากที่สุด) ของแอปพลิเคชันอันดับต้นๆ และสร้างเธรดเหล่านั้น SCHED_FIFO แทนที่จะเป็น SCHED_OTHER สิ่งนี้จะช่วยกำจัดความกระวนกระวายใจจาก UI และ RenderThreads ได้อย่างมีประสิทธิภาพ การติดตามที่เรารวบรวมด้วยตัวเลือกนี้เปิดใช้งานแสดงเวลาที่รันได้ตามลำดับไมโครวินาทีแทนที่จะเป็นมิลลิวินาที

อย่างไรก็ตาม เนื่องจากโหลดบาลานเซอร์ RT ไม่ได้รับรู้ถึงความจุ จึงทำให้ประสิทธิภาพการเริ่มต้นแอปพลิเคชันลดลง 30% เนื่องจากเธรด UI ที่รับผิดชอบในการเริ่มแอปจะถูกย้ายจากแกน Kryo ทอง 2.1Ghz ไปเป็นแกน Kryo สีเงิน 1.5GHz . ด้วยโหลดบาลานเซอร์ RT ที่รับรู้ความจุ เราจะเห็นประสิทธิภาพที่เทียบเท่าในการดำเนินการจำนวนมาก และการลดลง 10-15% ในเวลาเฟรมเปอร์เซ็นไทล์ที่ 95 และ 99 ในการวัดประสิทธิภาพ UI หลายๆ รายการของเรา

ขัดขวางการจราจร

เนื่องจากแพลตฟอร์ม ARM ส่งการขัดจังหวะไปยัง CPU 0 ตามค่าเริ่มต้นเท่านั้น เราขอแนะนำให้ใช้ IRQ balancer (irqbalance หรือ msm_irqbalance บนแพลตฟอร์ม Qualcomm)

ในระหว่างการพัฒนา Pixel เราพบว่ามีสาเหตุโดยตรงจาก CPU 0 ที่ล้นหลามด้วยการขัดจังหวะ ตัวอย่างเช่น หากเธรด mdss_fb0 ถูกกำหนดเวลาไว้บน CPU 0 มีความเป็นไปได้มากขึ้นที่จะเกิดปัญหาเนื่องจากการขัดจังหวะที่ทริกเกอร์โดยจอแสดงผลเกือบจะในทันทีก่อนการสแกนออก mdss_fb0 จะอยู่ในช่วงกลางของงานของตัวเองโดยมีกำหนดเวลาที่จำกัดมาก และจากนั้นก็จะเสียเวลาไปกับตัวจัดการการขัดจังหวะ MDSS ในตอนแรก เราพยายามแก้ไขปัญหานี้โดยการตั้งค่าความสัมพันธ์ของ CPU ของเธรด mdss_fb0 เป็น CPU 1-3 เพื่อหลีกเลี่ยงความขัดแย้งจากการขัดจังหวะ แต่แล้วเราก็ตระหนักว่าเรายังไม่ได้เปิดใช้งาน msm_irqbalance เมื่อเปิดใช้งาน msm_irqbalance jank ได้รับการปรับปรุงอย่างเห็นได้ชัด แม้ว่าทั้ง mdss_fb0 และการขัดจังหวะ MDSS จะอยู่บน CPU เดียวกัน เนื่องจากความขัดแย้งที่ลดลงจากการขัดจังหวะอื่นๆ

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

  • ทำให้ตัวจัดการขัดจังหวะเร็วขึ้น
  • ป้องกันไม่ให้เกิดการขัดจังหวะตั้งแต่แรก
  • เปลี่ยนความถี่ของการขัดจังหวะให้อยู่นอกเฟสกับงานปกติอื่นๆ ที่อาจรบกวน (หากเป็นการขัดจังหวะปกติ)
  • ตั้งค่า CPU affinity ของการขัดจังหวะโดยตรงและป้องกันไม่ให้สมดุล
  • ตั้งค่า CPU affinity ของเธรดที่การขัดจังหวะกำลังรบกวนเพื่อหลีกเลี่ยงการขัดจังหวะ
  • ใช้อินเทอร์รัปต์บาลานเซอร์เพื่อย้ายอินเทอร์รัปต์ไปยัง CPU ที่โหลดน้อยกว่า

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

irqs แบบยาว

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


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

ไดรเวอร์ที่ออกจากใบจองหรือ IRQ ถูกปิดใช้งานนานเกินไป

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

หลักเกณฑ์บางประการ:

  • หากเธรดที่รันได้คือ SCHED_FIFO และเธรดที่กำลังรันอยู่คือ SCHED_OTHER เธรดที่กำลังรันอยู่จะถูกปิดใช้งานการขัดจังหวะล่วงหน้าหรือการขัดจังหวะ
  • หากเธรดที่รันได้มีลำดับความสำคัญสูงกว่า (100) อย่างมีนัยสำคัญมากกว่าเธรดที่รันอยู่ (120) เธรดที่กำลังรันอยู่มีแนวโน้มที่จะมีการขอล่วงหน้าหรือการขัดจังหวะที่ปิดใช้งาน หากเธรดที่รันได้ไม่ทำงานภายในสองระยะเวลาอันสั้น
  • หากเธรดที่รันได้และเธรดที่กำลังรันอยู่มีลำดับความสำคัญเท่ากัน เธรดที่กำลังรันอยู่น่าจะมีการจองล่วงหน้าหรือการขัดจังหวะที่ปิดใช้งาน หากเธรดที่รันได้ไม่ทำงานภายใน 20ms

โปรดทราบว่าการเรียกใช้ตัวจัดการอินเทอร์รัปต์จะป้องกันไม่ให้คุณให้บริการอินเทอร์รัปต์อื่นๆ ซึ่งจะปิดใช้งานการจองล่วงหน้าด้วย


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

การใช้คิวงานไม่ถูกต้อง

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

อย่างไรก็ตาม คิวงานมักจะเป็นคำตอบที่ผิดสำหรับปัญหานี้เสมอ เนื่องจากคิวงานมักจะเป็น SCHED_OTHER เสมอ การขัดจังหวะด้วยฮาร์ดแวร์จำนวนมากอยู่ในเส้นทางวิกฤตของประสิทธิภาพ และต้องดำเนินการทันที Workqueues ไม่มีการรับประกันว่าจะดำเนินการเมื่อใด ทุกครั้งที่เราเห็นคิวงานในเส้นทางประสิทธิภาพที่สำคัญ คิวงานดังกล่าวเป็นสาเหตุของปัญหาที่เกิดขึ้นประปราย โดยไม่คำนึงถึงอุปกรณ์ บน Pixel ที่ใช้โปรเซสเซอร์ระดับเรือธง เราพบว่าคิวงานเดียวอาจล่าช้าได้ถึง 7 มิลลิวินาที หากอุปกรณ์อยู่ระหว่างโหลด ขึ้นอยู่กับพฤติกรรมของตัวกำหนดเวลาและสิ่งอื่นๆ ที่ทำงานบนระบบ

แทนที่จะเป็นคิวงาน ไดรเวอร์ที่ต้องการจัดการงานที่คล้ายกับการขัดจังหวะภายในเธรดที่แยกจากกันควรสร้าง kthread SCHED_FIFO ของตนเอง หากต้องการความช่วยเหลือในการดำเนินการนี้กับฟังก์ชัน kthread_work โปรดดูที่ แพตช์ นี้

การโต้แย้งการล็อคกรอบงาน

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

ความขัดแย้งล็อคเครื่องผูก

ในอดีต Binder มีการล็อคแบบโกลบอลเดียว หากเธรดที่รันธุรกรรม Binder ถูกจองล่วงหน้าในขณะที่ล็อคไว้ เธรดอื่นไม่สามารถดำเนินการธุรกรรม Binder ได้จนกว่าเธรดดั้งเดิมจะปลดล็อค นี้ไม่ดี; การโต้แย้งของ Binder สามารถบล็อกทุกสิ่งในระบบ รวมถึงการส่งการอัปเดต UI ไปยังจอแสดงผล (เธรด UI สื่อสารกับ SurfaceFlinger ผ่าน Binder)

Android 6.0 ได้รวมแพตช์หลายตัวเพื่อปรับปรุงพฤติกรรมนี้โดยการปิดการใช้งานการจองในขณะที่กดล็อค Binder ค้างไว้ สิ่งนี้ปลอดภัยเพียงเพราะว่าควรล็อค Binder ไว้สองสามไมโครวินาทีของรันไทม์จริง ประสิทธิภาพที่ได้รับการปรับปรุงอย่างมากในสถานการณ์ที่ไม่มีการโต้แย้ง และป้องกันการโต้แย้งโดยการป้องกันสวิตช์ตัวกำหนดตารางเวลาส่วนใหญ่ในขณะที่ล็อคเครื่องผูกไว้ อย่างไรก็ตาม ไม่สามารถปิดใช้งานการสำรองสำหรับรันไทม์ทั้งหมดของการล็อค Binder ค้างไว้ ซึ่งหมายความว่าการสำรองถูกเปิดใช้งานสำหรับฟังก์ชันที่สามารถเข้าสู่โหมดสลีปได้ (เช่น copy_from_user) ซึ่งอาจทำให้เกิดการสำรองเช่นเดียวกับกรณีดั้งเดิม เมื่อเราส่งแผ่นปะแก้ต้นทาง พวกเขาก็บอกเราทันทีว่านี่เป็นความคิดที่เลวร้ายที่สุดในประวัติศาสตร์ (เราเห็นด้วยกับพวกเขา แต่เราไม่สามารถโต้เถียงกับประสิทธิภาพของแพตช์ในการป้องกัน jank ได้)

การโต้แย้ง fd ภายในกระบวนการ

นี่เป็นของหายาก jank ของคุณอาจไม่ได้เกิดจากสิ่งนี้

ดังที่กล่าวไว้ หากคุณมีหลายเธรดภายในกระบวนการที่เขียน fd เดียวกัน ก็เป็นไปได้ที่จะเห็นข้อโต้แย้งใน fd นี้ อย่างไรก็ตาม ครั้งเดียวที่เราเห็นสิ่งนี้ระหว่างการนำ Pixel ขึ้นมาคือในระหว่างการทดสอบที่เธรดที่มีลำดับความสำคัญต่ำพยายามครอบครอง CPU ทั้งหมด เวลาในขณะที่เธรดที่มีลำดับความสำคัญสูงรันอยู่ในกระบวนการเดียวกัน เธรดทั้งหมดถูกเขียนไปยังเครื่องหมายการติดตาม fd และเธรดที่มีลำดับความสำคัญสูงอาจถูกบล็อกบนเครื่องหมายการติดตาม fd หากเธรดที่มีลำดับความสำคัญต่ำเก็บล็อค fd ไว้และถูกจองไว้ล่วงหน้า เมื่อการติดตามถูกปิดใช้งานจากเธรดที่มีลำดับความสำคัญต่ำ ไม่มีปัญหาด้านประสิทธิภาพการทำงาน

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

การเปลี่ยน CPU ที่ไม่ได้ใช้งานโดยไม่จำเป็น

เมื่อต้องจัดการกับ IPC โดยเฉพาะไปป์ไลน์แบบหลายกระบวนการ เป็นเรื่องปกติที่จะเห็นการเปลี่ยนแปลงของพฤติกรรมรันไทม์ต่อไปนี้:

  1. เธรด A ทำงานบน CPU 1
  2. เธรด A ปลุกเธรด B
  3. เธรด B เริ่มทำงานบน CPU 2
  4. เธรด A จะเข้าสู่โหมดสลีปทันที และจะถูกปลุกโดยเธรด B เมื่อเธรด B ทำงานปัจจุบันเสร็จแล้ว

แหล่งที่มาของค่าใช้จ่ายทั่วไปอยู่ระหว่างขั้นตอนที่ 2 และ 3 หาก CPU 2 ไม่ได้ใช้งาน จะต้องกลับสู่สถานะแอ็คทีฟก่อนที่เธรด B จะสามารถทำงานได้ ขึ้นอยู่กับ SOC และความลึกของช่วงไม่ได้ใช้งาน อาจใช้เวลาหลายสิบไมโครวินาทีก่อนที่เธรด B จะเริ่มทำงาน หากรันไทม์จริงของแต่ละด้านของ IPC ใกล้พอที่จะโอเวอร์เฮด ประสิทธิภาพโดยรวมของไปป์ไลน์นั้นสามารถลดลงได้อย่างมากด้วยการเปลี่ยน CPU ที่ไม่ได้ใช้งาน จุดที่พบบ่อยที่สุดสำหรับ Android ในการเข้าถึงสิ่งนี้คือบริเวณธุรกรรมของ Binder และบริการต่างๆ มากมายที่ใช้ Binder จะมีลักษณะเหมือนกับสถานการณ์ที่อธิบายไว้ข้างต้น

ขั้นแรก ให้ใช้ฟังก์ชัน wake_up_interruptible_sync() ในไดรเวอร์เคอร์เนลของคุณและสนับสนุนสิ่งนี้จากตัวกำหนดเวลาที่กำหนดเอง ถือว่านี่เป็นข้อกำหนด ไม่ใช่คำใบ้ Binder ใช้สิ่งนี้ในปัจจุบัน และช่วยได้มากกับธุรกรรม Binder แบบซิงโครนัส โดยหลีกเลี่ยงการเปลี่ยน CPU ที่ไม่ได้ใช้งานโดยไม่จำเป็น

ประการที่สอง ตรวจสอบให้แน่ใจว่าเวลาการเปลี่ยนแปลง cpuidle ของคุณเป็นไปตามความเป็นจริง และผู้ว่าการ cpuidle กำลังพิจารณาสิ่งเหล่านี้อย่างถูกต้อง หาก SOC ของคุณเข้าและออกจากสถานะไม่ได้ใช้งานที่ลึกที่สุด คุณจะไม่สามารถประหยัดพลังงานได้ด้วยการเข้าสู่สถานะไม่ได้ใช้งานที่ลึกที่สุด

การบันทึก

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

ปัญหาเกี่ยวกับ I/O

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

  • พินเนอร์เซอร์วิส เพิ่มใน Android 7.0 แล้ว PinnerService เปิดใช้งานเฟรมเวิร์กเพื่อล็อคไฟล์บางไฟล์ในแคชของหน้า การดำเนินการนี้จะลบหน่วยความจำเพื่อใช้โดยกระบวนการอื่น แต่หากมีไฟล์บางไฟล์ที่ทราบกันว่ามีการใช้เป็นประจำ การ mlock ไฟล์เหล่านั้นจะมีประสิทธิภาพ

    บนอุปกรณ์ Pixel และ Nexus 6P ที่ใช้ Android 7.0 เราได้ล็อกไฟล์ไว้สี่ไฟล์:
    • /system/framework/arm64/boot-framework.oat
    • /system/framework/oat/arm64/services.odex
    • /system/framework/arm64/boot.oat
    • /system/framework/arm64/boot-core-libart.oat
    ไฟล์เหล่านี้มีการใช้งานอย่างต่อเนื่องโดยแอปพลิเคชันและ system_server ส่วนใหญ่ ดังนั้นจึงไม่ควรเพจเอาท์ โดยเฉพาะอย่างยิ่ง เราพบว่าหากมีเพจใดเพจเอาท์ เพจเหล่านั้นจะถูกเพจกลับเข้าไปและทำให้เกิดปัญหาเมื่อเปลี่ยนจากแอปพลิเคชันรุ่นหนา
  • การเข้ารหัส อีกสาเหตุที่เป็นไปได้ของปัญหา I/O เราพบว่าการเข้ารหัสแบบอินไลน์ให้ประสิทธิภาพที่ดีที่สุดเมื่อเปรียบเทียบกับการเข้ารหัสที่ใช้ CPU หรือใช้บล็อกฮาร์ดแวร์ที่เข้าถึงได้ผ่าน DMA สิ่งสำคัญที่สุดคือ การเข้ารหัสแบบอินไลน์ช่วยลดความกระวนกระวายใจที่เกี่ยวข้องกับ I/O โดยเฉพาะเมื่อเปรียบเทียบกับการเข้ารหัสแบบ CPU เนื่องจากการดึงข้อมูลไปที่แคชของเพจมักจะอยู่ในเส้นทางวิกฤตของการเรนเดอร์ UI การเข้ารหัสตาม CPU จะแนะนำโหลด CPU เพิ่มเติมในเส้นทางวิกฤติ ซึ่งจะเพิ่มความกระวนกระวายใจมากกว่าการดึงข้อมูล I/O เท่านั้น

    เอ็นจิ้นการเข้ารหัสฮาร์ดแวร์ที่ใช้ DMA มีปัญหาที่คล้ายกัน เนื่องจากเคอร์เนลต้องใช้วงจรในการจัดการงานนั้น แม้ว่างานสำคัญอื่นๆ จะพร้อมให้รันก็ตาม เราขอแนะนำอย่างยิ่งให้ผู้จำหน่าย SOC สร้างฮาร์ดแวร์ใหม่เพื่อรองรับการเข้ารหัสแบบอินไลน์

การบรรจุงานขนาดเล็กเชิงรุก

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

แคชเพจฟาดฟัน

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

วิธีหนึ่งในการระบุสิ่งนี้คือการใช้ systrace โดยใช้แท็ก pagecache และฟีดที่ติดตามสคริปต์ที่ system/extras/pagecache/pagecache.py pagecache.py แปลคำขอแต่ละรายการเพื่อแมปไฟล์ลงในแคชของเพจเป็นสถิติรวมต่อไฟล์ หากคุณพบว่ามีการอ่านไฟล์จำนวนไบต์มากกว่าขนาดรวมของไฟล์นั้นบนดิสก์ แสดงว่าคุณกำลังโจมตีแคชของเพจอย่างแน่นอน

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

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

  • ใช้หน่วยความจำน้อยลงในกระบวนการต่อเนื่อง ยิ่งหน่วยความจำที่ใช้โดยกระบวนการถาวรน้อยลง หน่วยความจำที่พร้อมใช้งานสำหรับแอปพลิเคชันและแคชของเพจก็จะยิ่งมากขึ้นเท่านั้น
  • ตรวจสอบการแยกส่วนที่คุณมีสำหรับอุปกรณ์ของคุณเพื่อให้แน่ใจว่าคุณไม่ได้ถอดหน่วยความจำออกจากระบบปฏิบัติการโดยไม่จำเป็น เราเคยเห็นสถานการณ์ที่การแยกส่วนที่ใช้ในการดีบักทิ้งไว้โดยไม่ตั้งใจในการกำหนดค่าเคอร์เนลในการจัดส่ง ซึ่งใช้หน่วยความจำหลายสิบเมกะไบต์ สิ่งนี้สามารถสร้างความแตกต่างระหว่างการกดปุ่มแคชของเพจและไม่ใช่ โดยเฉพาะอย่างยิ่งบนอุปกรณ์ที่มีหน่วยความจำน้อยกว่า
  • หากคุณเห็นแคชของเพจกระทบกระเทือนใน system_server กับไฟล์สำคัญ ให้พิจารณาปักหมุดไฟล์เหล่านั้น วิธีนี้จะเพิ่มความกดดันด้านความจำในส่วนอื่น แต่อาจปรับเปลี่ยนพฤติกรรมได้มากพอที่จะหลีกเลี่ยงการฟาดฟัน
  • ปรับแต่ง lowmemorykiller ใหม่เพื่อพยายามรักษาหน่วยความจำให้ว่างมากขึ้น เกณฑ์ของ lowmemorykiller ขึ้นอยู่กับทั้งหน่วยความจำว่างสัมบูรณ์และแคชของหน้า ดังนั้นการเพิ่มเกณฑ์ที่กระบวนการในระดับ oom_adj ที่กำหนดถูกฆ่าอาจส่งผลให้มีการทำงานที่ดีขึ้นโดยส่งผลให้แอปพื้นหลังเสียชีวิตเพิ่มขึ้น
  • ลองใช้ ZRAM. เราใช้ ZRAM บน Pixel แม้ว่า Pixel จะมีขนาด 4GB ก็ตาม เนื่องจากสามารถช่วยได้กับหน้าที่สกปรกที่ไม่ค่อยได้ใช้