منتشر شد :
Android 12 (API Level 31) - Performance Hint API
Android 13 (API Level 33) - Performance Hint Manager در NDK API
(پیش نمایش) Android 15 (DP1) - reportActualWorkDuration()
با نکات عملکرد CPU، یک بازی می تواند بر رفتار عملکرد پویا CPU برای مطابقت بهتر با نیازهای آن تأثیر بگذارد. در اکثر دستگاهها، اندروید به صورت پویا سرعت ساعت CPU و نوع هسته را برای حجم کاری بر اساس خواستههای قبلی تنظیم میکند. اگر حجم کاری از منابع CPU بیشتری استفاده کند، سرعت کلاک افزایش مییابد و در نهایت حجم کاری به هسته بزرگتری منتقل میشود. اگر حجم کار از منابع کمتری استفاده کند، اندروید تخصیص منابع را کاهش میدهد. با ADPF، برنامه یا بازی می تواند سیگنال اضافی در مورد عملکرد و مهلت های خود ارسال کند. این به سیستم کمک می کند تا با شدت بیشتری افزایش یابد (بهبود عملکرد) و پس از اتمام بار کاری، ساعت را به سرعت پایین بیاورد (صرفه جویی در مصرف برق).
سرعت ساعت
وقتی دستگاه های اندرویدی به صورت پویا سرعت ساعت CPU خود را تنظیم می کنند، فرکانس می تواند عملکرد کد شما را تغییر دهد. طراحی کدی که به سرعت ساعت پویا می پردازد برای به حداکثر رساندن عملکرد، حفظ حالت حرارتی ایمن و استفاده کارآمد از توان مهم است. شما نمی توانید مستقیماً فرکانس های CPU را در کد برنامه خود اختصاص دهید. در نتیجه، یک راه معمول برای برنامهها برای اجرای با سرعتهای بالاتر ساعت CPU، اجرای یک حلقه مشغول در یک رشته پسزمینه است تا حجم کاری سختتر به نظر برسد. این عمل بدی است زیرا وقتی برنامه واقعاً از منابع اضافی استفاده نمیکند، انرژی را هدر میدهد و بار حرارتی روی دستگاه را افزایش میدهد. CPU PerformanceHint
API برای رفع این مشکل طراحی شده است. با آگاه کردن سیستم از مدت زمان واقعی کار و مدت زمان کار مورد نظر، Android میتواند یک نمای کلی از نیازهای CPU برنامه داشته باشد و منابع را به طور موثر تخصیص دهد. این منجر به عملکرد بهینه در سطح مصرف انرژی کارآمد می شود.
انواع هسته
انواع هسته های CPU که بازی شما روی آنها اجرا می شود یکی دیگر از فاکتورهای عملکرد مهم است. دستگاههای Android اغلب هسته CPU اختصاص داده شده به یک رشته را بر اساس رفتار بار کاری اخیر به صورت پویا تغییر میدهند. تخصیص هسته CPU در SoC هایی با انواع هسته های متعدد حتی پیچیده تر است. در برخی از این دستگاهها، هستههای بزرگتر تنها میتوانند برای مدت کوتاهی بدون وارد شدن به حالت حرارتی ناپایدار استفاده شوند.
بازی شما نباید به دلایل زیر سعی کند میل هسته CPU را تنظیم کند:
- بهترین نوع هسته برای حجم کاری بسته به مدل دستگاه متفاوت است.
- پایداری هسته های بزرگتر بر اساس SoC و راه حل های حرارتی مختلف ارائه شده توسط هر مدل دستگاه متفاوت است.
- تأثیر محیطی بر حالت حرارتی می تواند انتخاب اصلی را پیچیده تر کند. به عنوان مثال، آب و هوا یا یک قاب گوشی می تواند وضعیت حرارتی یک دستگاه را تغییر دهد.
- انتخاب هسته نمی تواند دستگاه های جدید با عملکرد اضافی و قابلیت های حرارتی را در خود جای دهد. در نتیجه، دستگاه ها اغلب میل پردازنده بازی را نادیده می گیرند.
نمونه ای از رفتار پیش فرض زمانبندی لینوکس
![رفتار زمانبندی لینوکس](https://developer.android.google.cn/static/games/optimize/adpf/images/adpf_linux_sched_behavior.png?hl=fa)
PerformanceHint API بیشتر از تاخیرهای DVFS خلاصه می شود
![ADPF بیشتر از تاخیرهای DVFS خلاصه می شود](https://developer.android.google.cn/static/games/optimize/adpf/images/adpf_dvfs_latency.png?hl=fa)
- اگر کارها باید روی یک CPU خاص اجرا شوند، PerformanceHint API می داند که چگونه از طرف شما این تصمیم را بگیرد.
- بنابراین، شما نیازی به استفاده از افینیت ندارید.
- دستگاه ها با توپولوژی های مختلف عرضه می شوند. قدرت و ویژگی های حرارتی آنقدر متنوع هستند که در معرض توسعه برنامه قرار نگیرند.
- شما نمی توانید هیچ فرضی در مورد سیستم زیربنایی که روی آن اجرا می کنید داشته باشید.
راه حل
ADPF کلاس PerformanceHintManager
را ارائه می دهد تا بازی ها بتوانند نکات عملکردی را برای سرعت ساعت CPU و نوع هسته به اندروید ارسال کنند. سپس سیستم عامل می تواند تصمیم بگیرد که چگونه از نکات بر اساس SoC و محلول حرارتی دستگاه استفاده کند. اگر برنامه شما از این API همراه با نظارت بر وضعیت حرارتی استفاده میکند، میتواند به جای استفاده از حلقههای مشغول و سایر تکنیکهای کدگذاری که میتواند باعث ایجاد throttling شود، نکات آگاهانهتری را به سیستم عامل ارائه دهد.
به این صورت است که یک بازی از نکات عملکرد استفاده می کند:
- جلسات راهنمایی برای موضوعات کلیدی که رفتار مشابهی دارند ایجاد کنید . به عنوان مثال:
- موضوع رندر و وابستگی های آن یک جلسه دریافت می کنند
- در Cocos، نخ موتور اصلی و نخ رندر یک جلسه می شود
- در یونیتی، افزونه Adaptive Performance Android Provider را ادغام کنید
- در Unreal، افزونه Unreal Adaptive Performance را ادغام کنید و از گزینه های مقیاس پذیری برای پشتیبانی از چندین سطح کیفیت استفاده کنید.
- موضوعات IO یک جلسه دیگر دریافت می کنند
- موضوعات صوتی جلسه سوم را دریافت می کنند
- موضوع رندر و وابستگی های آن یک جلسه دریافت می کنند
- بازی باید این کار را زودتر انجام دهد، حداقل 2 میلی ثانیه و ترجیحاً بیش از 4 میلی ثانیه قبل از اینکه یک جلسه نیاز به افزایش منابع سیستم داشته باشد.
- در هر جلسه راهنمایی، مدت زمان مورد نیاز برای اجرای هر جلسه را پیش بینی کنید. مدت زمان معمولی معادل یک بازه فریم است، اما اگر حجم کاری بین فریم ها به طور قابل توجهی تغییر نکند، برنامه می تواند از بازه زمانی کوتاه تری استفاده کند.
در اینجا نحوه عملی کردن تئوری آمده است:
PerformanceHintManager و createHintSession را راه اندازی کنید
مدیر را با استفاده از سرویس سیستم دریافت کنید و یک جلسه راهنمایی برای رشته یا گروه رشته خود ایجاد کنید که بر روی همان حجم کاری کار می کنند.
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 (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);
جاوا
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>
جاوا
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);