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, משתמשים בהגדרות ההתאמה כדי לשנות את רמות האיכות באופן דינמי.

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

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

אפשר לעקוב אחרי המצב התרמי של המכשיר באמצעות ה-method‏ 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);

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

אפשר לבקש מהמערכת את מרווח הטמפרטורה הנוכחי. כך אפשר לדעת עד כמה עומס העבודה קרוב להגבלת מהירות שעון המעבד (Thermal 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). כדי לעשות זאת, אפשר לרשום קריאה חוזרת כדי שהמערכת תעדכן אתכם בכל פעם שהסטטוס משתנה.

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);

חשוב לזכור להסיר את ה-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);

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

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

אפשר לעקוב אחרי המצב התרמי של המכשיר באמצעות ה-method‏ 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 במכשירים ישנים יותר