Performance Hint API

منتشر شد :

اندروید ۱۲ (سطح API 31) - PerformanceHintManager

اندروید ۱۳ (سطح API ۳۳) - مدیر نکات عملکرد در API NDK

اندروید ۱۵ (DP1) - reportActualWorkDuration()

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

سرعت ساعت

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

انواع هسته

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

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

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

مثالی از رفتار پیش‌فرض زمان‌بندی لینوکس

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

API PerformanceHint چیزی بیش از تأخیرهای DVFS را خلاصه می‌کند

چکیده‌های ADPF بیشتر از تأخیرهای DVFS
شکل ۲. ADPF می‌داند چگونه بهترین تصمیم را از طرف شما بگیرد
  • اگر وظایف نیاز به اجرا روی یک CPU خاص داشته باشند، PerformanceHint API می‌داند که چگونه این تصمیم را از طرف شما بگیرد.
  • بنابراین، نیازی به استفاده از میل ترکیبی ندارید.
  • دستگاه‌ها با توپولوژی‌های مختلفی عرضه می‌شوند؛ ویژگی‌های توان و حرارت آنها بسیار متنوع است و نمی‌توان آنها را در اختیار توسعه‌دهندگان برنامه قرار داد.
  • شما نمی‌توانید هیچ فرضی در مورد سیستمی که روی آن اجرا می‌کنید، داشته باشید.

راه حل

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

در اینجا نحوه‌ی پیاده‌سازی این تئوری در عمل آمده است:

مقداردهی اولیه PerformanceHintManager و createHintSession

با استفاده از سرویس سیستم، مدیر را دریافت کنید و یک جلسه راهنمایی برای نخ یا گروه نخ خود که روی همان حجم کار کار می‌کنند، ایجاد کنید.

سی++

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

در صورت لزوم، نخ‌ها را تنظیم کنید

منتشر شد :

اندروید ۱۱ (سطح API ۳۴)

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

سی++

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

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

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

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

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

سی++

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

برای مثال:

سی++

// 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 فراخوانی کنید تا به سیستم اطلاع دهید تا سیستم عامل بتواند منابع را مطابق با هدف جدید تنظیم کند. لازم نیست آن را در هر فریم فراخوانی کنید و فقط زمانی که مدت زمان هدف تغییر می‌کند، باید آن را فراخوانی کنید.

سی++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

جاوا

hintSession.updateTargetWorkDuration(targetDuration);