תאריך הפרסום:
Android 12 (רמת API 31) – Performance Hint API
Android 13 (רמת API 33) – Performance Hint Manager ב-NDK API
(תצוגה מקדימה) Android 15 (DP1) – reportActualWorkDuration()
בעזרת רמזים לביצועי CPU, אפליקציה יכולה להשפיע על התנהגות דינמית של ביצועי CPU כדי להתאים אותה טוב יותר לצרכים שלה. ברוב המכשירים, מערכת Android מתאימה באופן דינמי את מהירות שעון המעבד ואת סוג הליבה לעומס עבודה על סמך הדרישות הקודמות. אם עומס עבודה משתמש ביותר משאבי CPU, מהירות השעון עולה ועומס העבודה עובר בסופו של דבר לליבה גדולה יותר. אם עומס העבודה משתמש בפחות משאבים, מערכת Android מקטינה את הקצאת המשאבים. בעזרת ADPF, אפליקציה יכולה לשלוח אות נוסף לגבי הביצועים ומועדי היעד שלה. כך המערכת יכולה להגביר את המהירות בצורה אגרסיבית יותר (לשפר את הביצועים) ולהוריד את מהירות השעון במהירות כשהעומס מסתיים (לחסוך בצריכת החשמל).
מהירות שעון
כשמכשירי Android משנים באופן דינמי את מהירות השעון של המעבד, התדירות יכולה לשנות את הביצועים של הקוד. חשוב לתכנן קוד שמתייחס למהירויות שעון דינמיות כדי למקסם את הביצועים, לשמור על מצב תרמי בטוח ולהשתמש בחשמל בצורה יעילה. אי אפשר להקצות תדרי CPU ישירות בקוד האפליקציה. כתוצאה מכך, דרך נפוצה לאפליקציות לנסות לפעול במהירויות שעון גבוהות יותר של המעבד היא להריץ לולאה עמוסה בשרשור ברקע, כך שעומס העבודה נראה תובעני יותר. זהו נוהג לא מומלץ כי הוא מבזב אנרגיה ומגדיל את העומס התרמי על המכשיר כשהאפליקציה לא משתמשת בפועל במשאבים הנוספים. ה-API של המעבד PerformanceHint
נועד לפתור את הבעיה הזו.
אם תציינו למערכת את משך העבודה בפועל ואת משך העבודה הרצוי, מערכת Android תוכל לקבל סקירה כללית של צורכי המעבד של האפליקציה ולהקצות משאבים בצורה יעילה. כך תגיעו לביצועים אופטימליים ברמת צריכת חשמל יעילה.
סוגים עיקריים
סוגי ליבות המעבד שהאפליקציה פועלת עליהן הם עוד גורם חשוב שמשפיע על הביצועים. במכשירי Android, ליבת המעבד שמוקצית לשרשור משתנה לעיתים קרובות באופן דינמי על סמך התנהגות עומס העבודה האחרונה. הקצאת ליבות CPU מורכבת עוד יותר במערכות על שבב (SoC) עם כמה סוגי ליבות. בחלק מהמכשירים האלה, אפשר להשתמש בליבות הגדולות רק לזמן קצר בלי להיכנס למצב לא יציב מבחינת טמפרטורה.
האפליקציה לא צריכה לנסות להגדיר את הקשר בין ליבת המעבד לבין תהליך מסוים מהסיבות הבאות:
- סוג הליבה הכי טוב לעומס עבודה משתנה בהתאם לדגם המכשיר.
- היכולת להפעיל ליבות גדולות יותר משתנה בהתאם ל-SoC ולפתרונות התרמיים השונים שכלולים בכל דגם מכשיר.
- ההשפעה הסביבתית על המצב התרמי יכולה לסבך עוד יותר את הבחירה של ליבת המחשב. לדוגמה, מזג האוויר או כיסוי לטלפון יכולים לשנות את מצב הטמפרטורה של המכשיר.
- אי אפשר לבחור מכשירים חדשים עם ביצועים ויכולות תרמיות נוספים. כתוצאה מכך, המכשירים מתעלמים לעיתים קרובות מהזיקה של האפליקציה למעבד.
דוגמה להתנהגות ברירת המחדל של מתזמן ב-Linux

ה-API של PerformanceHint מפשט יותר מאשר את זמני האחזור של DVFS

- אם המשימות צריכות לפעול במעבד ספציפי, ממשק PerformanceHint API יודע איך לקבל את ההחלטה הזו בשמכם.
- לכן, אין צורך להשתמש בהעדפה.
- למכשירים יש טופולוגיות שונות. מאפייני ההספק והמאפיינים התרמיים משתנים מדי, ולכן הם לא מוצגים למפתחי אפליקציות.
- אי אפשר להניח הנחות לגבי המערכת הבסיסית שבה אתם משתמשים.
הפתרון
ה-API ADPF מספק את המחלקה PerformanceHintManager
כדי שאפליקציות יוכלו לשלוח ל-Android רמזים לשיפור הביצועים לגבי מהירות השעון של המעבד וסוג הליבה. לאחר מכן, מערכת ההפעלה יכולה להחליט איך הכי טוב להשתמש ברמזים על סמך ה-SoC והפתרון התרמי של המכשיר. אם האפליקציה משתמשת ב-API הזה יחד עם מעקב אחרי מצב טמפרטורה, היא יכולה לספק רמזים מושכלים יותר למערכת ההפעלה במקום להשתמש בלולאות עמוסות ובטכניקות קידוד אחרות שעלולות לגרום להגבלת קצב העברת הנתונים.
כך עוברים מתיאוריה למעשים:
מאתחלים את PerformanceHintManager ויוצרים hintSession
מקבלים את המנהל באמצעות שירות המערכת ויוצרים סשן של רמזים לשרשור או לקבוצת השרשורים שעובדים על אותה עומס עבודה.
C++
int32_t tids[1];
tids[0] = gettid();
int64_t target_fps_nanos = getFpsNanos();
APerformanceHintManager* hint_manager = APerformanceHint_getManager();
APerformanceHintSession* hint_session =
APerformanceHint_createSession(hint_manager, tids, 1, target_fps_nanos);
Java
int[] tids = {
android.os.Process.myTid()
};
long targetFpsNanos = getFpsNanos();
PerformanceHintManager performanceHintManager =
(PerformanceHintManager) this.getSystemService(Context.PERFORMANCE_HINT_SERVICE);
PerformanceHintManager.Session hintSession =
performanceHintManager.createHintSession(tids, targetFpsNanos);
אם צריך, מגדירים שרשורים
תאריך הפרסום:
Android 11 (רמת API 34)
משתמשים בפונקציה setThreads
של PerformanceHintManager.Session
כשרוצים להוסיף בהמשך שרשורים אחרים. לדוגמה, אם יוצרים את השרשור של הפיזיקה בשלב מאוחר יותר וצריך להוסיף אותו לסשן, אפשר להשתמש ב-API setThreads
הזה.
C++
auto tids = thread_ids.data();
std::size_t size = thread_ids_.size();
APerformanceHint_setThreads(hint_session, tids, size);
Java
int[] tids = new int[3];
// add all your thread IDs. Remember to use android.os.Process.myTid() as that
// is the linux native thread-id.
// Thread.currentThread().getId() will not work because it is jvm's thread-id.
hintSession.setThreads(tids);
אם אתם מטרגטים רמות API נמוכות יותר, תצטרכו להרוס את הסשן וליצור סשן חדש בכל פעם שתצטרכו לשנות את מזהי השרשור.
דיווח על משך העבודה בפועל
המערכת עוקבת אחרי משך הזמן בפועל שנדרש להשלמת העבודה בננו-שניות, ומדווחת על כך בסיום העבודה בכל מחזור. לדוגמה, אם מדובר בשרשורי הרינדור, צריך להפעיל את הפונקציה הזו בכל פריים.
כדי לקבל את השעה בפועל באופן מהימן, משתמשים ב:
C++
clock_gettime(CLOCK_MONOTONIC, &clock); // if you prefer "C" way from <time.h>
// or
std::chrono::high_resolution_clock::now(); // if you prefer "C++" way from <chrono>
Java
System.nanoTime();
לדוגמה:
C++
// All timings should be from `std::chrono::steady_clock` or `clock_gettime(CLOCK_MONOTONIC, ...)`
auto start_time = std::chrono::high_resolution_clock::now();
// do work
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count();
int64_t actual_duration = static_cast<int64_t>(duration);
APerformanceHint_reportActualWorkDuration(hint_session, actual_duration);
Java
long startTime = System.nanoTime();
// do work
long endTime = System.nanoTime();
long duration = endTime - startTime;
hintSession.reportActualWorkDuration(duration);
עדכון משך העבודה המתוכנן כשצריך
בכל פעם שמשך העבודה של היעד משתנה, למשל אם המשתמש בוחר יעד שונה של פריימים לשנייה, צריך להפעיל את השיטה updateTargetWorkDuration
כדי לעדכן את המערכת, כך שמערכת ההפעלה תוכל להתאים את המשאבים בהתאם ליעד החדש. לא צריך להפעיל אותה בכל פריים, אלא רק כשמשך היעד משתנה.
C++
APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);
Java
hintSession.updateTargetWorkDuration(targetDuration);