זיהוי תנודות חדות שקשורות לתנודות (jitter)

תנודות (jitter) הן התנהגות מערכת אקראית שמונעת את הפעלת העבודה. בדף הזה נסביר איך לזהות בעיות שקשורות לתנודות באיכות הווידאו ולטפל בהן.

עיכוב של מתזמן השרשור באפליקציה

עיכוב של מתזמן הוא הסימפטום הבולט ביותר של רעידות: תהליך שצריך להריץ הופך לניתן להרצה, אבל לא פועל במשך זמן רב. מידת החשיבות של העיכוב משתנה בהתאם להקשר. לדוגמה:

  • סביר להניח שאפשר לדחות שרשור עזר אקראי באפליקציה למשך אלפיות שנייה רבות בלי בעיה.
  • יכול להיות ששרשור ממשק המשתמש של האפליקציה יוכל לסבול תנודות של 1-2 אלפיות השנייה.
  • אם שרתי kthread של הנהג פועלים בתור SCHED_FIFO, הם עלולים לגרום לבעיות אם הם ניתנים להפעלה למשך 500us לפני ההפעלה.

אפשר לזהות את זמני הרצינות ב-systrace לפי העמודה הכחולה שמופיעה לפני קטע ריצה של חוט. אפשר גם לקבוע את זמן ההרצה לפי משך הזמן שחולף בין האירוע sched_wakeup של חוט לבין האירוע sched_switch שמסמן את תחילת ביצוע החוטים.

שרשורים שפועלים יותר מדי זמן

שרשראות של ממשק המשתמש באפליקציה שפועלות במשך זמן רב מדי עלולות לגרום לבעיות. בדרך כלל, לשרשור ברמה נמוכה עם זמני ריצה ארוכים יש סיבות שונות, אבל כדי לנסות להקטין את זמן הריצה של שרשור ממשק המשתמש לאפס, יכול להיות שתצטרכו לתקן חלק מהבעיות שגורמות לשרשור ברמה נמוכה להגיע לזמני ריצה ארוכים. כדי לצמצם את העיכובים:

  1. משתמשים ב-cpusets כפי שמתואר בקטע בקרת קצב עיבוד נתונים תרמית.
  2. מגדילים את הערך של CONFIG_HZ.
    • בעבר, הערך הוגדר ל-100 בפלטפורמות arm ו-arm64. עם זאת, זהו מקרה היסטורי, ולא כדאי להשתמש בו במכשירים אינטראקטיביים. הערך CONFIG_HZ=100 מציין ש-jiffy הוא 10 אלפיות השנייה, כלומר איזון העומסים בין מעבדים עשוי להימשך 20 אלפיות השנייה (שני jiffies). זה יכול לתרום באופן משמעותי לתנודות במערכת עמוסה.
    • במכשירים מהדור האחרון (Nexus 5X,‏ Nexus 6P,‏ Pixel ו-Pixel XL) הוגדרה כברירת מחדל הערך CONFIG_HZ=300. צריכת החשמל לא אמורה לגדול באופן משמעותי, אבל זמני הריצה צפויים להשתפר משמעותית. אם אתם רואים עלייה משמעותית בצריכת החשמל או בעיות בביצועים אחרי שינוי של CONFIG_HZ, סביר להניח שאחד מהדרייברים שלכם משתמש בטימר שמבוסס על jiffies גולמיים במקום על אלפיות השנייה, ומבצע המרה ל-jiffies. בדרך כלל אפשר לפתור את הבעיה בקלות (ראו את תיקון לתיקון בעיות של טיימר kgsl ב-Nexus 5X וב-6P במהלך המרה ל-CONFIG_HZ=300).
    • לבסוף, בדקנו את ההגדרה CONFIG_HZ=1000 ב-Nexus/Pixel וגילינו שהיא מאפשרת לשפר את הביצועים ולצמצם את צריכת האנרגיה באופן משמעותי, בגלל הפחתת העומס של RCU.

בעקבות שני השינויים האלה בלבד, זמן הריצה של שרשור ממשק המשתמש במכשיר אמור להשתפר משמעותית במצב עומס.

שימוש ב-sys.use_fifo_ui

אפשר לנסות להגדיר את המאפיין sys.use_fifo_ui לערך 1 כדי להקטין את זמן ההרצה של רצף המשימות של ממשק המשתמש לאפס.

אזהרה: אל תשתמשו באפשרות הזו בהגדרות מעבדים הטרוגניות, אלא אם יש לכם מתזמן RT שמתחשב ביכולת. בנוסף, בשלב הזה אין מתזמן RT פעיל שמתחשב ביכולת עיבוד. אנחנו עובדים על תכונה כזו ל-EAS, אבל היא עדיין לא זמינה. מתזמן ברירת המחדל של RT מבוסס אך ורק על העדיפויות של RT ועל כך שכבר יש ל-CPU חוט RT בעדיפות זהה או גבוהה יותר.

כתוצאה מכך, מתזמן ה-RT שמוגדר כברירת מחדל ישמח להעביר את חוט ה-UI שפועל זמן רב יחסית, ממרכז גדול בתדירות גבוהה למרכז קטן בתדירות מינימלית, אם חוט kthread בעדיפות FIFO גבוהה יותר מתעורר באותו מרכז גדול. הדבר יוביל לנסיגה משמעותית בביצועים. האפשרות הזו עדיין לא נמצאה בשימוש במכשיר Android למשלוח, ולכן אם ברצונך להשתמש בה, עליך לפנות לצוות ביצועי Android כדי לקבל עזרה באימות שלה.

כשהאפשרות sys.use_fifo_ui מופעלת, המערכת עוקבת אחרי שרשור ממשק המשתמש ו-RenderThread (שני השרשורים הקריטיים ביותר לממשק המשתמש) של האפליקציה העליונה, ומגדירה את השרשורים האלה כ-SCHED_FIFO במקום כ-SCHED_OTHER. כך אפשר למנוע ביעילות רעידות ב-UI וב-RenderThreads. הטראסים שיצאנו עם הפעלת האפשרות הזו מראים זמני ריצה בסדר גודל של מיקרו-שניות במקום אלפיות השנייה.

עם זאת, מאחר שמאזן העומסים של RT לא היה מודע לקיבולת, הייתה ירידה של 30% בביצועים של הפעלת האפליקציה כי שרשור ממשק המשתמש שאחראי להפעלת האפליקציה הועבר מליבת Kryo ברמת Gold של 2.1GHz לליבת Kryo ברמת Silver של 1.5GHz. כשמשתמשים במאזן עומסים מסוג RT עם תמיכה בקיבולת, אנחנו רואים ביצועים דומים בפעולות בכמות גדולה, וירידה של 10-15% בזמני הפריימים של 95% ו-99% במדדי הביצועים הרבים של ממשק המשתמש שלנו.

תנועה מופסקת

מאחר שפלטפורמות ARM מעבירות הפסקות ל-CPU 0 רק כברירת מחדל, מומלץ להשתמש במאזן IRQ (irqbalance או msm_irqbalance בפלטפורמות Qualcomm).

במהלך הפיתוח של Pixel, ראינו תנודות בתנועה שאפשר לשייך ישירות להתקפות על המעבד (CPU) 0. לדוגמה, אם הפעילות של mdss_fb0 תוזמנה ב-CPU 0, הייתה סבירות גבוהה יותר לתנודות בגלל הפסקה (interrupt) שמופעל על ידי המסך כמעט מיד לפני סיום הסריקה. mdss_fb0 תהיה באמצע העבודה שלה עם תאריך יעד קצר מאוד, ואז היא תאבד זמן מסוים לטיפול בהפרעה של MDSS. בהתחלה ניסינו לפתור את הבעיה על ידי הגדרת הקשר ל-CPU של חוט mdss_fb0 ל-CPUs‏ 1-3 כדי למנוע תחרות עם ההפרעה, אבל אז הבנו שעדיין לא הפעלנו את msm_irqbalance. כשהתכונה msm_irqbalance מופעלת, יש שיפור משמעותי בתנודות החדות (jank) גם כשגם mdss_fb0 וגם ההפרעה של MDSS נמצאים באותו מעבד, בגלל הפחתת התחרות מפני הפרעות אחרות.

אפשר לזהות את הבעיה ב-systrace על ידי בדיקה של הקטע sched וגם של הקטע irq. בקטע sched מוצג מה תוזמנה, אבל אם יש חפיפה באזור בקטע irq, סימן שהתרחשה השהיה בזמן הזה במקום התהליך המתוזמן הרגיל. אם אתם רואים קטעי זמן משמעותיים במהלך ההפרעה, תוכלו לבחור באחת מהאפשרויות הבאות:

  • לגרום לטיפול בהפרעה לפעול מהר יותר.
  • למנוע את ההפרעה מלכתחילה.
  • לשנות את תדירות ההפרעה כך שלא תהיה באותו פאזה עם משימות רגילות אחרות שהיא עלולה להפריע להן (אם מדובר בהפרעה קבועה).
  • הגדרה ישירה של העדפת המעבד (CPU) להפרעה, כדי למנוע איזון.
  • כדי למנוע את ההפרעה, מגדירים את ההתאמה ל-CPU של הליבה שבה מתבצע ההפרעה.
  • להסתמך על מאזן ההפרעות כדי להעביר את ההפרעה ל-CPU פחות עמוס.

באופן כללי לא מומלץ להגדיר שיוך ל-CPU, אבל הוא יכול להיות שימושי במקרים ספציפיים. באופן כללי, קשה מאוד לחזות את מצב המערכת ברוב ההפרעות הנפוצות, אבל אם יש לכם קבוצה ספציפית מאוד של תנאים שמפעילים הפרעות מסוימות שבהן המערכת מוגבלת יותר מהרגיל (למשל VR), שיוך מפורש ל-CPU עשוי להיות פתרון טוב.

אירועי softirq ארוכים

בזמן שפעולת softirq, היא משביתה את היכולת לבצע חלוקת זמן מראש. אפשר גם להפעיל softirqs במקומות רבים בתוך הליבה, והן יכולות לפעול בתוך תהליך של משתמש. אם יש מספיק פעילות של softirq, תהליכי המשתמש יפסיקו להריץ softirqs, ו-ksoftirqd יתעורר כדי להריץ softirqs ולבצע איזון עומסים. בדרך כלל זה בסדר. עם זאת, קריאה אחת ארוכה מאוד ל-softirq יכולה לגרום להרס במערכת.


אירועי softirq גלויים בקטע irq של המעקב, כך שקל לזהות אותם אם אפשר לשחזר את הבעיה במהלך המעקב. מכיוון ש-softirq יכול לפעול בתוך תהליך משתמש, softirq שגוי יכול להתבטא גם כזמן ריצה נוסף בתוך תהליך משתמש ללא סיבה ברורה. אם מופיע הסטטוס הזה, צריך לבדוק את הקטע irq כדי לראות אם הבעיה נובעת מ-softirqs.

מנהלי התקנים שמשאירים את היכולת לקבל עדיפות או את ה-IRQs מושבתים למשך זמן רב מדי

השבתה של ביטול משימות או הפסקות לזמן ארוך מדי (עשרות אלפיות השנייה) גורמת לתנודות. בדרך כלל, התנודות מופיעות כאשר חוט מסוים הופך לזמין להרצה אבל לא פועל ב-CPU מסוים, גם אם לחוט הזמין להרצה יש עדיפות גבוהה יותר (או SCHED_FIFO) בהשוואה לחוט השני.

הנחיות:

  • אם השרשור שאפשר להריץ הוא SCHED_FIFO והשרשור שפועל הוא SCHED_OTHER, השרשור שפועל לא מאפשר עדיפות או הפסקות.
  • אם לשרשור שאפשר להריץ יש עדיפות גבוהה בהרבה (100) מזו של השרשור שפועל (120), סביר להניח שההשהיה או ההפרעות של השרשור שפועל מושבתות אם השרשור שאפשר להריץ לא פועל תוך שתי jiffies.
  • אם לשרשור שניתן להריץ ולשרשור שפועל יש אותה רמת עדיפות, סביר להניח שההשבתה מראש או ההפרעות הושבתו בשרשור שפועל אם השרשור שניתן להריץ לא פועל תוך 20 אלפיות השנייה.

חשוב לזכור שהפעלת טיפול בהפרעה מונעת טיפול בהפרעות אחרות, וגם משביתה את היכולת לבצע עדיפות.


אפשרות נוספת לזיהוי האזורים הבעייתיים היא באמצעות ה-tracer preemptirqsoff (ראו שימוש ב-ftrace דינמי). הכלי הזה יכול לספק תובנות משמעותיות יותר לגבי הסיבה העיקרית לאזור שלא ניתן להפרעה (למשל, שמות פונקציות), אבל כדי להפעיל אותו צריך לבצע פעולות פולשניות יותר. יכול להיות שהשינוי הזה ישפיע יותר על הביצועים, אבל כדאי מאוד לנסות אותו.

שימוש שגוי בעומסי עבודה

לעתים קרובות, מנהלי ההפרעות צריכים לבצע עבודה שיכולה לפעול מחוץ להקשר של ההפרעה, כדי לאפשר העברה של עבודה לשרשראות שונות בליבה. מפתחי מנהלי התקנים עשויים להבחין בליבה שיש לה פונקציונליות נוחה מאוד של משימות אסינכררוניות ברמת המערכת שנקראת workqueues, ויכול להיות שהם ישתמשו בה לצורך עבודה שקשורה להפסקות.

עם זאת, כמעט תמיד אין טעם להשתמש בעומסי עבודה כדי לפתור את הבעיה הזו, כי הם תמיד בסטטוס SCHED_OTHER. הרבה הפסקות חומרה נמצאות בנתיב הביצועים הקריטי, וצריך להריץ אותן באופן מיידי. אין לכם ערובה למועד שבו תתבצע ההרצה של תור העבודה. בכל פעם שזיהינו עומס עבודה בנתיב הביצועים הקריטי, הוא היה מקור לתנודות תנועה פתאומות (jank) באופן לא סדיר, ללא קשר למכשיר. ב-Pixel, עם מעבד הדגל, ראינו שיכול להיות עיכוב של עד 7 אלפיות השנייה בעבודה של workqueue יחיד אם המכשיר נמצא בעומס, בהתאם להתנהגות של מתזמן המשימות ודברים אחרים שפועלים במערכת.

במקום workqueue, מנהלי התקנים שצריכים לטפל בעבודה שדומה להפרעה בתוך שרשור נפרד צריכים ליצור שרשור kthread משלהם מסוג SCHED_FIFO. לקבלת עזרה בביצוע הפעולה הזו באמצעות פונקציות kthread_work, אפשר לעיין בתיקון הזה.

תחרות על נעילה של Framework

מאבקי נעילת מסגרת יכולים להיות מקור לתנודות חדות או לבעיות אחרות בביצועים. בדרך כלל הוא נגרם על ידי נעילת ActivityManagerService, אבל הוא יכול להופיע גם בנעילות אחרות. לדוגמה, הנעילה של PowerManagerService יכולה להשפיע על הביצועים של המסך במצב מופעל. אם אתם רואים את ההודעה הזו במכשיר, אין פתרון טוב כי אפשר לשפר את המצב רק באמצעות שיפורים ארכיטקטוניים במסגרת. עם זאת, אם משנים קוד שפועל בתוך system_server, חשוב מאוד להימנע מחסימה של מנעולים למשך זמן רב, במיוחד של המנעול של ActivityManagerService.

תחרות על נעילה של Binder

בעבר, ל-binder היה מנעול גלובלי יחיד. אם השרשור שמריץ עסקה של מקשר הופסק בזמן שהוא מחזיק את הנעילה, אף שרשור אחר לא יכול לבצע עסקה של מקשר עד שהשרשור המקורי ישחרר את הנעילה. זה לא טוב. מאבקי שליטה ב-binder עלולים לחסום את כל המערכת, כולל שליחת עדכוני ממשק משתמש למסך (שרשורי ממשק המשתמש מתקשרים עם SurfaceFlinger דרך binder).

בגרסה 6.0 של Android נוספו כמה תיקונים כדי לשפר את ההתנהגות הזו על ידי השבתת העדיפות המוקדם בזמן שמחזיקים את נעילת ה-binder. זה היה בטוח רק כי נעילת ה-binder אמורה להישאר נעולה למשך כמה מיקרו-שניות בלבד במהלך זמן הריצה בפועל. השינוי הזה שיפר באופן משמעותי את הביצועים במצבים ללא תחרות, ומנע מצב של תחרות על ידי מניעת רוב המעברים של מתזמן המשימות בזמן שהנעילה של ה-binder נמשכת. עם זאת, לא ניתן להשבית את העדיפות המוקדם לכל משך זמן הריצה של נעילת ה-binder, כלומר העדיפות המוקדם הופעלה לפונקציות שיכולות להיכנס למצב שינה (כמו copy_from_user), דבר שעלול לגרום לאותה עדיפות מוקדם כמו במקרה המקורי. כששלחנו את התיקונים ל-upstream, הם הודיעו לנו מיד שזו הרעיון הכי גרוע בהיסטוריה. (הסכמנו איתם, אבל לא יכולנו להתווכח עם היעילות של התיקונים במניעת תנודות).

התחרות על fd בתוך תהליך

זה מקרה נדיר. סביר להניח שהבעיה לא נובעת מכך.

עם זאת, אם יש כמה שרשורים בתוך תהליך שכותבים לאותו fd, ייתכן שתראו תחרות על ה-fd הזה. עם זאת, הפעם היחידה שראינו את זה במהלך ההרצה הראשונית של Pixel הייתה במהלך בדיקה שבה שרשורים בעדיפות נמוכה ניסו לתפוס את כל זמן המעבד בזמן ששרשור יחיד בעדיפות גבוהה רץ באותו תהליך. כל השרשור כתבו ב-fd של סמן המעקב, והשרשור בעדיפות גבוהה יכול היה להיחסם ב-fd של סמן המעקב אם שרשור בעדיפות נמוכה החזיק את נעילת ה-fd ולאחר מכן הוחלף. כשהמעקב הושבת בשרשורים בעדיפות נמוכה, לא הייתה בעיה בביצועים.

לא הצלחנו לשחזר את הבעיה בשום מצב אחר, אבל כדאי לציין אותה כגורם פוטנציאלי לבעיות בביצועים במהלך המעקב.

מעברים מיותרים למצב חוסר פעילות במעבד (CPU)

כשעובדים עם IPC, במיוחד עם צינורות עיבוד נתונים עם כמה תהליכים, בדרך כלל רואים וריאציות על התנהגות זמן הריצה הבאה:

  1. שרשור א' פועל במעבד 1.
  2. שרשור א' מעיר את שרשור ב'.
  3. אשכול B מתחיל לפעול במעבד 2.
  4. חוט א' עובר מיידית למצב שינה, וחוט ב' מעיר אותו כשחוט ב' מסיים את העבודה הנוכחית.

מקור נפוץ של תקורה הוא בין שלבים 2 ל-3. אם מעבד 2 לא פעיל, צריך להחזיר אותו למצב פעיל כדי שאפשר יהיה להריץ את הליבה B. בהתאם ל-SOC ולעומק המצב הלא פעיל, יכול להיות שיעברו עשרות מיקרו-שניות לפני שחווט B יתחיל לפעול. אם זמן הריצה בפועל של כל צד של ה-IPC קרוב מספיק לעלויות הנוספות, יכול להיות שתנודות במצב הפעילות של המעבד יגרמו לירידה משמעותית בביצועים הכוללים של צינור עיבוד הנתונים. המצב הזה נפוץ ב-Android בעיקר בעסקאות של binder, ושירותים רבים שמשתמשים ב-binder מגיעים למצב שמתואר למעלה.

קודם כול, משתמשים בפונקציה wake_up_interruptible_sync() במנהלי הליבה ותומכים בכך מכל מתזמן מותאם אישית. חשוב להתייחס לדרישה הזו כדרישה ולא כהמלצה. Binder משתמש בזה היום, והוא עוזר מאוד בעסקאות סינכרניות של Binder, ומאפשר להימנע ממעברים מיותרים של מעבדים למצב חוסר פעילות.

שנית, חשוב לוודא שזמני המעבר של cpuidle ריאליים ושמנהל ה-cpuidle מתייחס אליהם בצורה נכונה. אם ה-SOC שלכם עובר טרישה (thrashing) במעבר בין מצב ההשבתה העמוק ביותר למצב פעיל, לא תוכלו לחסוך באנרגיה על ידי מעבר למצב ההשבתה העמוק ביותר.

רישום

רישום ביומן צורך בזיכרון או במחזורי מעבד (CPU), לכן אל תספגו את מאגר הנתונים של היומן בספאם. עלויות הרישום ביומן מתבצעות באפליקציה (באופן ישיר) וב-daemon של היומן. לפני ששולחים את המכשיר, צריך להסיר את יומני ניפוי הבאגים.

בעיות בקלט/פלט

פעולות קלט/פלט (I/O) הן מקורות נפוצים לתנודות. אם חוט גישה לקובץ שממופה לזיכרון והדף לא נמצא במטמון הדפים, מתרחשת שגיאה והדף נקרא מהדיסק. הפעולה הזו חוסמת את השרשור (בדרך כלל למשך יותר מ-10 אלפיות השנייה), ואם היא מתרחשת בנתיב הקריטי של עיבוד ממשק המשתמש, היא עלולה לגרום לתנודות בתנועה. יש יותר מדי סיבות לפעולות קלט/פלט כדי לדון בהן כאן, אבל כדאי לבדוק את המיקומים הבאים כשמנסים לשפר את התנהגות הקלט/הפלט:

  • PinnerService. השירות PinnerService נוסף ב-Android 7.0, והוא מאפשר למסגרת לנעול קבצים מסוימים במטמון הדפים. הפעולה הזו מסירה את הזיכרון לשימוש של כל תהליך אחר, אבל אם יש קבצים שידוע מראש שנעשה בהם שימוש באופן קבוע, כדאי להשתמש ב-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 משתמשים בקובצים האלה כל הזמן, ולכן לא צריך להוציא אותם מהזיכרון. במיוחד, גילינו שאם אחת מהן תועבר ל-page out, היא תוחזר ל-page in ותגרום לתנודות בזמן המעבר מאפליקציה כבדה.
  • הצפנה. סיבה אפשרית נוספת לבעיות קלט/פלט. לפי הממצאים שלנו, הצפנה בתוך שורה (inline) מספקת את הביצועים הטובים ביותר בהשוואה להצפנה שמבוססת על מעבד או להצפנה באמצעות בלוק חומרה שניתן לגשת אליו באמצעות DMA. והכי חשוב, הצפנה בתוך שורת הקוד מפחיתה את התנודות (jitter) שמשויכות ל-I/O, במיוחד בהשוואה להצפנה שמבוססת על מעבד. מכיוון שהאחזור של מטמון הדף נמצא לעיתים קרובות בנתיב הקריטי של עיבוד ממשק המשתמש, הצפנה מבוססת-מעבד מוסיפה עומס נוסף על המעבד בנתיב הקריטי, וכך מוסיפה עוד יותר רעידות מאשר אחזור הקלט/פלט בלבד.

    מנועי הצפנה לחומרה מבוססי-DMA נתקלים בבעיה דומה, כי הליבה צריכה להקדיש מחזורי זמן לניהול העבודה הזו גם אם יש עבודה קריטית אחרת שאפשר להריץ. אנחנו ממליצים מאוד לכל ספק SOC שמפתח חומרה חדשה לכלול תמיכה בהצפנה מוטמעת.

אריזה אגרסיבית של משימות קטנות

חלק מתזמני המשימות מציעים תמיכה באריזה של משימות קטנות בליבות מעבד יחידות, כדי לנסות לצמצם את צריכת החשמל על ידי שמירה על יותר מעבדים במצב חוסר פעילות למשך זמן ארוך יותר. השיטה הזו עובדת טוב מבחינת תעבורת הנתונים וצריכת החשמל, אבל היא יכולה להשפיע דרסטית על זמן האחזור. יש כמה שרשורים קצרים בנתיב הקריטי של עיבוד הגרפי של ממשק המשתמש, שאפשר להתייחס אליהם כאל שרשורים קטנים. אם השרשורים האלה יתעכבו בזמן ההעברה האיטית שלהם למעבדים אחרים, הם יגרמו לבעיות בממשק (jank). מומלץ להשתמש באריזה של משימות קטנות באופן שמרני מאוד.

טרישה במטמון הדפים

מכשיר ללא מספיק זיכרון פנוי עשוי לפתע לפעול באיטיות רבה בזמן ביצוע פעולה ממושכת, כמו פתיחת אפליקציה חדשה. מעקב אחר האפליקציה עשוי לגלות שהיא חסומה באופן קבוע בקלט/פלט במהלך הפעלה מסוימת, גם אם היא לא חסומה בקלט/פלט לעיתים קרובות. בדרך כלל זה סימן לפעילות מוגזמת במטמון הדפים, במיוחד במכשירים עם פחות זיכרון.

אחת הדרכים לזהות את הבעיה היא לבצע מעקב systrace באמצעות התג pagecache ולהעביר את המעקב לסקריפט ב-system/extras/pagecache/pagecache.py. הפונקציה pagecache.py מתרגמת בקשות נפרדות למיפוי קבצים במטמון הדפים לנתונים סטטיסטיים מצטברים לכל קובץ. אם גיליתם שנקראו יותר בייטים של קובץ מאשר הגודל הכולל של הקובץ בדיסק, סימן שאתם נתקלים בבעיה של טרישה (thrashing) במטמון דפי הנתונים.

פירוש הדבר הוא שקבוצת העבודה שנדרשת לעומס העבודה (בדרך כלל אפליקציה אחת ו-system_server) גדולה מכמות הזיכרון שזמינה למטמון הדפים במכשיר. כתוצאה מכך, בזמן שחלק אחד של עומס העבודה מקבל את הנתונים שהוא צריך במטמון הדפים, חלק אחר שיעשה בו שימוש בעתיד הקרוב יוסר מהמטמון ויהיה צורך לאחזר אותו שוב, וכך הבעיה תתרחש שוב עד שהטעינה תושלם. זו הסיבה העיקרית לבעיות בביצועים כשאין מספיק זיכרון במכשיר.

אין דרך מושלמת לתקן את הבעיה של טרחה בזיכרון המטמון של דפים, אבל יש כמה דרכים לנסות לשפר את המצב במכשיר נתון.

  • שימוש בפחות זיכרון בתהליכים מתמידים. ככל שפחות זיכרון מנוצל על ידי תהליכים קבועים, כך יש יותר זיכרון זמין לאפליקציות ולמטמון הדפים.
  • בודקים את ההחרגות של המכשיר כדי לוודא שלא מסירים זיכרון ממערכת ההפעלה שלא לצורך. נתקלנו במצבים שבהם קטעי קוד ששימשו לניפוי באגים נותרו בטעות בהגדרות הליבה של גרסת הייצור, והם צורכים עשרות מגה-בייט של זיכרון. הדבר יכול להשפיע על האפשרות של פגיעה ב-thrashing של מטמון הדפים, במיוחד במכשירים עם פחות זיכרון.
  • אם אתם רואים טרישה של מטמון דפים ב-system_server בקבצים קריטיים, כדאי להצמיד את הקבצים האלה. הפעולה הזו תגדיל את לחץ הזיכרון במקומות אחרים, אבל עשויה לשנות את ההתנהגות מספיק כדי למנוע טרישה.
  • משנים את ההגדרות של lowmemorykiller כדי לנסות לשמור על יותר זיכרון פנוי. ערכי הסף של lowmemorykiller מבוססים גם על זיכרון פנוי מוחלט וגם על מטמון הדפים, כך שהגדלת הסף שבו תהליכים ברמה מסוימת של oom_adj ייפסקו עשויה להוביל להתנהגות טובה יותר על חשבון הפסקה של יותר אפליקציות ברקע.
  • כדאי לנסות להשתמש ב-ZRAM. אנחנו משתמשים ב-ZRAM ב-Pixel, למרות ש-Pixel כולל 4GB, כי הוא יכול לעזור עם דפים מלוכלכים שמשמשים לעתים רחוקות.