Thermal API

תאריך הפרסום:

‫Android 11 (רמת API‏ 30) – Thermal API

‫Android 12 (רמת API‏ 31) – NDK API

(גרסת טרום-השקה) Android 15‏ (DP1) – getThermalHeadroomThresholds()

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

למנועי משחקים יש בדרך כלל פרמטרים של ביצועים בזמן ריצה, שאפשר להשתמש בהם כדי לשנות את עומס העבודה שהמנוע מפעיל על המכשיר. לדוגמה, הפרמטרים האלה יכולים להגדיר את מספר ה-worker threads, את הקשר בין ה-worker threads לליבות גדולות וקטנות, את אפשרויות הדיוק של ה-GPU ואת הרזולוציות של ה-framebuffer. ב-Unity Engine, מפתחי משחקים יכולים לשנות את עומס העבודה על ידי שינוי הגדרות האיכות באמצעות הפלאגין Adaptive Performance. ב-Unreal Engine, משתמשים בהגדרות ההתאמה כדי לשנות את רמות האיכות באופן דינמי.

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

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

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

ADPF Thermal API Pre-Integration
איור 1. מרווח תרמי בלי מעקב פעיל getThermalHeadroom
ADPF Thermal API Post-Integration
איור 2. מרווח תרמי עם מעקב פעיל אחרי `getThermalHeadroom`

רכישת Thermal Manager

כדי להשתמש ב-Thermal API, קודם צריך לקבל את Thermal Manager

C++‎

AThermalManager* thermal_manager = AThermal_acquireManager();

Java

PowerManager powerManager = (PowerManager)this.getSystemService(Context.POWER_SERVICE);

שאילתה לגבי מרווח הטמפרטורה

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

התוצאה נעה בין 0.0f (ללא הגבלת קצב, THERMAL_STATUS_NONE)

‫1.0f (הגבלת מהירות חמורה, THERMAL_STATUS_SEVERE). אם יש לכם רמות שונות של איכות גרפיקה במשחקים, אתם יכולים לפעול לפי ההנחיות שלנו בנושא מרווח תרמי.

C++‎

float thermal_headroom = AThermal_getThermalHeadroom(0);
ALOGI("ThermalHeadroom: %f", thermal_headroom);

Java

float thermalHeadroom = powerManager.getThermalHeadroom(0);
Log.d("ADPF", "ThermalHeadroom: " + thermalHeadroom);

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

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

C++‎

AThermalStatus thermal_status = AThermal_getCurrentThermalStatus(thermal_manager);
ALOGI("ThermalStatus is: %d", thermal_status);

Java

int thermalStatus = powerManager.getCurrentThermalStatus();
Log.d("ADPF", "ThermalStatus is: " + thermalStatus);

קבלת התראות על שינויים בסטטוס הטמפרטורה

אפשר גם להימנע מבדיקת הסטטוס של thermalHeadroom עד שthermalStatus מגיע לרמה מסוימת (לדוגמה: THERMAL_STATUS_LIGHT). כדי לעשות זאת, אפשר לרשום קריאה חוזרת (callback) כדי שהמערכת תשלח לכם התראה בכל פעם שהסטטוס משתנה.

C++‎

int result = AThermal_registerThermalStatusListener(thermal_manager, callback);
if ( result != 0 ) {
  // failed, check whether you have previously registered callback that
  // hasn’t been unregistered
}

Java

// PowerManager.OnThermalStatusChangedListener is an interface, thus you can
// also define a class that implements the methods
PowerManager.OnThermalStatusChangedListener listener = new
  PowerManager.OnThermalStatusChangedListener() {
    @Override
    public void onThermalStatusChanged(int status) {
        Log.d("ADPF", "ThermalStatus changed: " + status);
        // check the status and flip the flag to start/stop pooling when
        // applicable
    }
};
powerManager.addThermalStatusListener(listener);

חשוב לזכור להסיר את מאזין האירועים בסיום

C++‎

int result = AThermal_unregisterThermalStatusListener(thermal_manager, callback);
if ( result != 0 ) {
  // failed, check whether the callback has been registered previously
}

Java

powerManager.removeThermalStatusListener(listener);

ניקוי

אחרי שמסיימים, צריך לנקות את thermal_manager שקיבלתם. אם אתם משתמשים ב-Java, אפשר לבצע איסוף אוטומטי של הפניה ל-PowerManager. אבל אם אתם משתמשים ב-Java API דרך JNI ושמרתם הפניה, אל תשכחו לנקות את ההפניה.

C++‎

AThermal_releaseManager(thermal_manager);

בקטע Integrate Thermal API בcodelab בנושא התאמה יש מדריך מלא להטמעה של Thermal API במשחק C++‎ מקורי באמצעות C++‎ API‏ (NDK API) ו-Java API (דרך JNI).

הנחיות לגבי מרווח טמפרטורה

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

סטטוסים של טמפרטורה והמשמעות שלהם

מגבלות של Thermal API במכשירים

יש כמה מגבלות ידועות או דרישות נוספות של Thermal API, בגלל הטמעות של Thermal API במכשירים ישנים יותר. אלה המגבלות והפתרונות האפשריים:

  • אל תבצעו קריאות ל-API‏ GetThermalHeadroom() בתדירות גבוהה מדי. אם תעשו זאת, ה-API יחזיר NaN. לא מומלץ להפעיל אותה יותר מפעם אחת כל 10 שניות.
  • מומלץ להימנע מקריאה ל-API מכמה שרשורים, כי קשה יותר לוודא את תדירות הקריאה, וה-API עלול להחזיר את הערך NaN.
  • אם הערך ההתחלתי של GetThermalHeadroom() הוא NaN, ה-API לא זמין במכשיר
  • אם הפונקציה GetThermalHeadroom() מחזירה ערך גבוה (למשל: 0.85 ומעלה) והפונקציה GetCurrentThermalStatus() עדיין מחזירה THERMAL_STATUS_NONE, סביר להניח שהסטטוס לא עודכן. אפשר להשתמש בהיוריסטיקה כדי להעריך את הסטטוס הנכון של ויסות התדרים (Thermal Throttling), או פשוט להשתמש ב-getThermalHeadroom() בלי getCurrentThermalStatus().

דוגמה להיוריסטיקה:

  1. בודקים אם יש תמיכה ב-Thermal API. ‫isAPISupported() בודקת את הערך של הקריאה הראשונה אל getThermalHeadroom כדי לוודא שהוא לא 0 או NaN, ומדלגת על השימוש בממשק ה-API אם הערך הראשון הוא 0 או NaN.
  2. אם הפונקציה getCurrentThermalStatus() מחזירה ערך שונה מ-THERMAL_STATUS_NONE, המכשיר עובר ויסות תרמי.
  3. אם הפקודה getCurrentThermalStatus() ממשיכה להחזיר את הערך THERMAL_STATUS_NONE, זה לא בהכרח אומר שלא מתבצעת במכשיר הגבלת מהירות בגלל חום. יכול להיות שהמכשיר לא תומך ב-getCurrentThermalStatus(). בודקים את ערך ההחזרה של getThermalHeadroom() כדי לוודא את מצב המכשיר.
  4. אם הפונקציה getThermalHeadroom() מחזירה ערך גדול מ-1.0, יכול להיות שהסטטוס הוא THERMAL_STATUS_SEVERE או גבוה יותר. במקרה כזה, צריך להפחית את עומס העבודה באופן מיידי ולשמור על עומס עבודה נמוך יותר עד שהפונקציה getThermalHeadroom() תחזיר ערך נמוך יותר.
  5. אם הפונקציה getThermalHeadroom() מחזירה ערך של 0.95, יכול להיות שהסטטוס הוא THERMAL_STATUS_MODERATE או גבוה יותר. במקרה כזה, צריך לצמצם את עומס העבודה באופן מיידי ולהמשיך לעקוב כדי למנוע קריאה גבוהה יותר.
  6. אם הפונקציה getThermalHeadroom() מחזירה ערך של 0.85, יכול להיות שהסטטוס הוא THERMAL_STATUS_LIGHT. כדאי להמשיך לעקוב אחרי המצב ולצמצם את עומס העבודה אם אפשר.

קוד מדומה:

  bool isAPISupported() {
    float first_value_of_thermal_headroom = getThermalHeadroom();
    if ( first_value_of_thermal_headroom == 0 ||
      first_value_of_thermal_headroom == NaN ) {
        // Checked the thermal Headroom API's initial return value
        // it is NaN or 0,so, return false (not supported)
        return false;
    }
    return true;
  }

  if (!isAPISupported()) {
    // Checked the thermal Headroom API's initial return value, it is NaN or 0
    // Don’t use the API
  } else {
      // Use thermalStatus API to check if it returns valid values.
      if (getCurrentThermalStatus() > THERMAL_STATUS_NONE) {
          // The device IS being thermally throttled
      } else {
      // The device is not being thermally throttled currently. However, it
      // could also be an indicator that the ThermalStatus API may not be
      // supported in the device.
      // Currently this API uses predefined threshold values for thermal status
      // mapping. In the future  you may be able to query this directly.
      float thermal_headroom = getThermalHeadroom();
      if ( thermal_headroom > 1.0) {
            // The device COULD be severely throttled.
      } else  if ( thermal_headroom > 0.95) {
            // The device COULD be moderately throttled.
      } else if ( thermal_headroom > 0.85) {
            // The device COULD be experiencing light throttling.
      }
    }
  }

תרשים:

דוגמה להיוריסטיקה של ADPF
איור 3. דוגמה להיוריסטיקה לקביעת התמיכה ב-Thermal API במכשירים ישנים יותר