Performance Hint API

منتشرشده:

‫Android 12 (سطح میانای برنامه‌سازی کاربردی ۳۱) - PerformanceHintManager

Android 13 (سطح میانای برنامه‌سازی کاربردی ۳۳) - مدیر اشاره عملکرد در NDK API

‫Android 15 (DP1) - reportActualWorkDuration()

با نکته‌های عملکرد CPU، برنامه می‌تواند بر رفتار عملکرد پویای CPU تأثیر بگذارد تا بهتر با نیازهایش مطابقت داشته باشد. در اکثر دستگاه‌ها، Android به‌طور پویا سرعت ساعت واحد پردازش مرکزی و نوع هسته را برای حجم کاری براساس درخواست‌های قبلی تنظیم می‌کند. اگر حجم کاری از منابع واحد پردازش مرکزی بیشتری استفاده کند، سرعت ساعت افزایش می‌یابد و حجم کاری درنهایت به هسته بزرگ‌تری منتقل می‌شود. اگر بار کاری از منابع کمتری استفاده کند، Android تخصیص منابع را کاهش می‌دهد. با ADPF، برنامه می‌تواند نشان اضافی درباره عملکرد و مهلت‌هایش ارسال کند. این کار به سیستم کمک می‌کند تا با سرعت بیشتری به حداکثر عملکرد برسد (بهبود عملکرد) و وقتی بار کاری تکمیل شد، سرعت ساعت را به‌سرعت کاهش دهد (صرفه‌جویی در مصرف برق).

سرعت ساعت

وقتی دستگاه‌های Android به‌طور پویا سرعت ساعت CPU را تنظیم می‌کنند، بسامد می‌تواند عملکرد کد شما را تغییر دهد. طراحی کدی که سرعت‌های ساعت پویا را درنظر بگیرد برای بیشینه کردن عملکرد، حفظ وضعیت حرارتی ایمن، و استفاده کارآمد از توان مهم است. نمی‌توانید مستقیماً در کد برنامه خود فرکانس‌های CPU را اختصاص دهید. در نتیجه، روشی رایج برای برنامه‌ها جهت تلاش برای اجرا در سرعت‌های ساعت CPU بالاتر، اجرای حلقه مشغول در رشته پس‌زمینه است تا بار کاری پرتقاضاتر به‌نظر برسد. این کار روشی بد است زیرا باعث هدر رفتن انرژی می‌شود و بار حرارتی دستگاه را در زمانی که برنامه درواقع از منابع اضافی استفاده نمی‌کند افزایش می‌دهد. ‫CPU PerformanceHint API برای رفع این مشکل طراحی شده است. با اطلاع دادن مدت زمان واقعی کار و مدت زمان هدف کار به سیستم، Android می‌تواند نمای کلی از نیازهای واحد پردازش مرکزی برنامه را دریافت کند و منابع را به‌طور کارآمد اختصاص دهد. این کار منجر به عملکرد بهینه در سطح مصرف برق کارآمد می‌شود.

انواع هسته

نوع هسته‌های CPU که برنامه شما روی آن‌ها اجرا می‌شود عامل مهم دیگری در عملکرد است. دستگاه‌های Android اغلب هسته CPU اختصاص‌داده‌شده به یک رشته را به‌صورت پویا براساس رفتار حجم کار اخیر تغییر می‌دهند. تخصیص هسته CPU در SoC با انواع هسته‌های متعدد حتی پیچیده‌تر است. در برخی‌از این دستگاه‌ها، هسته‌های بزرگ‌تر فقط می‌توانند برای مدت کوتاهی بدون ورود به حالت حرارتی ناپایدار استفاده شوند.

برنامه شما نباید به دلایل زیر سعی کند وابستگی هسته CPU را تنظیم کند:

  • بهترین نوع هسته برای حجم کار براساس مدل دستگاه متفاوت است.
  • پایداری اجرای هسته‌های بزرگ‌تر براساس SoC و راهکارهای حرارتی مختلف ارائه‌شده توسط هر مدل دستگاه متفاوت است.
  • تأثیر زیست‌محیطی بر وضعیت حرارتی می‌تواند انتخاب هسته را پیچیده‌تر کند. برای مثال، آب‌وهوا یا قاب تلفن می‌تواند وضعیت حرارتی دستگاه را تغییر دهد.
  • انتخاب هسته نمی‌تواند دستگاه‌های جدید با عملکرد بیشتر و قابلیت‌های حرارتی را در خود جای دهد. درنتیجه، دستگاه‌ها اغلب وابستگی پردازنده برنامه را نادیده می‌گیرند.

نمونه‌ای از رفتار زمان‌بندی پیش‌فرض Linux

رفتار زمان‌بند Linux
شکل ۱. «فرماندار» می‌تواند حدود ۲۰۰ میلی‌ثانیه طول بکشد تا فرکانس CPU را افزایش یا کاهش دهد. ‫ADPF با سیستم «مقیاس‌بندی پویا ولتاژ و فرکانس» (DVFS) کار می‌کند تا بهترین عملکرد را در هر وات ارائه دهد

«میانای برنامه‌سازی کاربردی PerformanceHint» تأخیرهای DVFS را انتزاعی‌تر می‌کند

‫ADPF بیشتر از «تأخیرهای مقیاس‌بندی فرکانس و ولتاژ پویا» خلاصه می‌کند
شکل ۲. چارچوب عملکرد پویای Android می‌داند چگونه بهترین تصمیم را ازجانب شما بگیرد
  • اگر وظایف باید در یک CPU خاص اجرا شوند، PerformanceHint 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);

جاوا

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 (سطح میانای برنامه‌سازی کاربردی ۳۴)

وقتی رشته‌های دیگری دارید که باید بعداً اضافه شوند، از تابع setThreads PerformanceHintManager.Session استفاده کنید. برای مثال، اگر رشته فیزیک خود را بعداً ایجاد کنید و لازم باشد آن را به جلسه اضافه کنید، می‌توانید از این setThreads API استفاده کنید.

C++‎

auto tids = thread_ids.data();
std::size_t size = thread_ids_.size();
APerformanceHint_setThreads(hint_session, tids, size);

جاوا

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

اگر «سطوح میانای برنامه‌سازی کاربردی» پایین‌تری را هدف‌یابی می‌کنید، باید هر بار که می‌خواهید شناسه‌های رشته را تغییر دهید، جلسه را ازبین ببرید و جلسه جدیدی ایجاد کنید.

گزارش کردن مدت واقعی کار

مدت‌زمان واقعی موردنیاز برای تکمیل کار را برحسب نانوثانیه ردیابی کنید و پس‌از تکمیل کار در هر چرخه، آن را به سیستم گزارش دهید. برای مثال، اگر این برای رشته‌های پردازنده شما است، این را در هر قاب فراخوانی کنید.

برای دریافت زمان واقعی به‌طور قابل‌اعتماد، از موارد زیر استفاده کنید:

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>

جاوا

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

جاوا

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

جاوا

hintSession.updateTargetWorkDuration(targetDuration);