تجزیه و تحلیل با نمایه GPU Rendering

ابزار Profile GPU Rendering زمان نسبی هر مرحله از فرآیند رندرینگ برای رندر فریم قبلی را نشان می‌دهد. این دانش می‌تواند به شما در شناسایی گلوگاه‌های موجود در فرآیند رندرینگ کمک کند تا بتوانید عملکرد رندرینگ برنامه خود را بهینه کنید.

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

نمایش بصری

ابزار Profile GPU Rendering مراحل و زمان‌های نسبی آنها را به شکل یک نمودار نمایش می‌دهد: یک هیستوگرام رنگی. شکل 1 نمونه‌ای از چنین نمایشی را نشان می‌دهد.

نمودار رندرینگ پردازنده گرافیکی پروفایل
شکل ۱. نمودار رندرینگ پردازنده گرافیکی پروفایل

هر بخش از هر نوار عمودی نمایش داده شده در نمودار Profile GPU Rendering، مرحله‌ای از خط لوله را نشان می‌دهد و با استفاده از یک رنگ خاص در نمودار میله‌ای برجسته شده است. شکل 2 کلید فهم معنای هر رنگ نمایش داده شده را نشان می‌دهد.

رندرینگ پردازنده گرافیکی پروفایل، شرح نمودار
شکل ۲. شرح نمودار رندرینگ پردازنده گرافیکی پروفایل

وقتی فهمیدید هر رنگ چه معنایی دارد، می‌توانید جنبه‌های خاصی از برنامه خود را هدف قرار دهید تا عملکرد رندر آن را بهینه کنید.

مراحل و معانی آنها

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

مدیریت ورودی

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

وقتی این بخش بزرگ باشد

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

پیمایش در یک LazyColumn یا LazyRow نیز می‌تواند در این مرحله ظاهر شود. هنگامی که لمس کاربر به عنوان پیمایش (scroll) در نظر گرفته می‌شود، فهرست تنبل (lazy list) از رویدادهای لمسی برای ترکیب و چیدمان پویای آیتم‌ها استفاده می‌کند. اگر برنامه شما در پاسخ به تغییرات موقعیت پیمایش ، کارهای سفارشی انجام می‌دهد، مهم است که این عملیات را تا حد امکان سریع انجام دهید تا از افت فریم جلوگیری شود. ابزارهای پروفایلینگ مانند CPU Profiler در اندروید استودیو یا Perfetto می‌توانند به شما در بررسی بیشتر کمک کنند. برای اطلاعات بیشتر به نمای کلی ردیابی سیستم (system tracing) مراجعه کنید.

انیمیشن‌ها

مرحله انیمیشن‌ها به شما نشان می‌دهد که ارزیابی تمام حالت‌های انیمیشن که در آن فریم اجرا می‌شدند چقدر طول کشیده است. برخی از APIهای رایج انیمیشن در Compose عبارتند از animate*AsState ، Transition و Animatable . علاوه بر این، Recomposer در طول این مرحله اجرا می‌شود تا تغییرات حالت snapshot را پردازش کرده و ترکیب‌ها را به‌روزرسانی کند. این بدان معناست که سربار ترکیب مجدد اغلب مستقیماً در مرحله انیمیشن ظاهر می‌شود.

برای رابط‌های کاربری Jetpack Compose، کتابخانه Compose Runtime Tracing را اضافه کنید تا بتوانید ردپاهای ترکیب‌بندی دقیق را در کنار رویدادهای سیستم مشاهده کنید.

وقتی این بخش بزرگ باشد

مقادیر بالا در این ناحیه معمولاً نتیجه‌ی کاری است که به دلیل تغییرات حالت ناشی از انیمیشن اجرا می‌شود. برای مثال، یک انیمیشن fling که LazyColumn یا LazyRow شما را پیمایش می‌کند، باعث ترکیب، اندازه‌گیری و تخصیص سریع موارد جدید لیست می‌شود.

اندازه گیری

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

ابتدا، سیستم گره‌های طرح‌بندی را اندازه‌گیری می‌کند. هر ترکیب‌پذیر (composable) دارای محدودیت‌ها و اصلاح‌کننده‌های خاصی است که محدودیت‌های اندازه شیء روی صفحه را توصیف می‌کنند. برخی از ترکیب‌پذیرها می‌توانند اندازه مشخص و ثابتی داشته باشند؛ برخی دیگر اندازه‌ای دارند که با محدودیت‌های منتقل شده توسط ظرف طرح‌بندی والد سازگار می‌شود.

دوم، سیستم گره‌های طرح‌بندی را قرار می‌دهد. هنگامی که Compose اندازه گره‌های فرزند را در طول مرحله اندازه‌گیری محاسبه کرد، می‌تواند مرحله جایگذاری را ادامه دهد، که در آن گره‌های طرح‌بندی را روی صفحه نمایش اندازه و موقعیت‌دهی می‌کند.

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

وقتی این بخش بزرگ باشد

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

از CPU Profiler در اندروید استودیو یا Perfetto برای بررسی مراحل طرح‌بندی و شناسایی گلوگاه‌ها استفاده کنید. برای اطلاعات بیشتر به مرور کلی ردیابی سیستم مراجعه کنید.

قرعه کشی

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

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

وقتی این بخش بزرگ باشد

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

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

آپلود

معیار آپلود، مدت زمان لازم برای انتقال اشیاء بیت‌مپ از حافظه CPU به حافظه GPU در طول فریم فعلی را نشان می‌دهد.

از آنجایی که پردازنده‌های CPU و GPU متفاوت هستند، هر کدام از آنها دارای بخش‌های RAM متفاوتی برای پردازش هستند. وقتی در اندروید یک بیت‌مپ رسم می‌کنید، سیستم قبل از اینکه GPU بتواند آن را روی صفحه نمایش رندر کند، بیت‌مپ را به حافظه GPU منتقل می‌کند. سپس، GPU بیت‌مپ را در حافظه پنهان (cache) ذخیره می‌کند تا سیستم نیازی به انتقال مجدد داده‌ها نداشته باشد، مگر اینکه بافت از حافظه پنهان بافت GPU حذف شود.

نکته: در دستگاه‌های لالی‌پاپ، این مرحله بنفش است.

وقتی این بخش بزرگ باشد

تمام منابع یک فریم قبل از اینکه بتوانند برای ترسیم یک فریم استفاده شوند، باید در حافظه GPU قرار گیرند. این بدان معناست که مقدار بالای این معیار می‌تواند به معنای تعداد زیادی بار منابع کوچک یا تعداد کمی منابع بسیار بزرگ باشد. یک مورد رایج زمانی است که یک برنامه یک بیت‌مپ واحد را نمایش می‌دهد که نزدیک به اندازه صفحه نمایش است. مورد دیگر زمانی است که یک برنامه تعداد زیادی تصویر بندانگشتی را نمایش می‌دهد.

برای کوچک کردن این نوار، می‌توانید از تکنیک‌هایی مانند موارد زیر استفاده کنید:

  • مطمئن شوید که وضوح تصویر بیت‌مپ شما خیلی بزرگتر از اندازه‌ای که نمایش داده می‌شوند، نباشد. برای مثال، از نمایش یک تصویر 1024x1024 به صورت 48x48 خودداری کنید.
  • با بهره‌گیری از کتابخانه‌های مدرن مانند Coil ، می‌توان قبل از مرحله همگام‌سازی بعدی، یک بیت‌مپ را به صورت غیرهمزمان از قبل بارگذاری کرد.

صدور دستورات

بخش دستورات مربوط به issue، زمان لازم برای صدور تمام دستورات لازم برای ترسیم لیست‌های نمایشی روی صفحه را نشان می‌دهد.

برای اینکه سیستم بتواند لیست‌های نمایشی را روی صفحه نمایش رسم کند، دستورات لازم را به GPU ارسال می‌کند. معمولاً این عمل را از طریق OpenGL ES API انجام می‌دهد.

این فرآیند مدتی طول می‌کشد، زیرا سیستم قبل از ارسال دستور به GPU، تبدیل نهایی و برش را برای هر دستور انجام می‌دهد. سپس سربار اضافی در سمت GPU ایجاد می‌شود که دستورات نهایی را محاسبه می‌کند. این دستورات شامل تبدیل‌های نهایی و برش اضافی هستند.

وقتی این بخش بزرگ باشد

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

for (i in 0 until 1000) {
    canvas.drawPoint()
}

صدور آن بسیار گران‌تر از موارد زیر است:

canvas.drawPoints(thousandPointArray)

همیشه یک همبستگی یک به یک بین صدور دستورات و فهرست‌های نمایش ترسیم وجود ندارد. برخلاف نوار دستورات مسئله که زمان لازم برای ارسال دستورات ترسیم به GPU را ثبت می‌کند، معیار ترسیم نشان دهنده زمانی است که صرف ثبت دستورات صادر شده در فهرست نمایش شده است.

این تفاوت به این دلیل ایجاد می‌شود که لیست‌های نمایش در هر کجا که ممکن باشد توسط سیستم ذخیره می‌شوند. در نتیجه، موقعیت‌هایی وجود دارد که در آن‌ها یک اسکرول، تبدیل یا انیمیشن نیاز به ارسال مجدد لیست نمایش توسط سیستم دارد، اما نیازی به بازسازی آن - گرفتن مجدد دستورات ترسیم - از ابتدا نیست. در نتیجه، می‌توانید نوار دستورات با مشکل بالا را بدون دیدن نوار دستورات با مشکل بالا ببینید.

بافرها را تعویض کنید

به محض اینکه اندروید ارسال لیست نمایش خود را به پردازنده گرافیکی (GPU) به پایان رساند، سیستم یک دستور نهایی صادر می‌کند تا به درایور گرافیک بگوید که کار با فریم فعلی تمام شده است. در این مرحله، درایور می‌تواند سرانجام تصویر به‌روز شده را به صفحه نمایش دهد.

وقتی این بخش بزرگ باشد

درک این نکته مهم است که پردازنده گرافیکی (GPU) کار را به صورت موازی با پردازنده مرکزی (CPU) انجام می‌دهد. سیستم اندروید دستورات ترسیم را به پردازنده گرافیکی (GPU) ارسال می‌کند و سپس به سراغ کار بعدی می‌رود. پردازنده گرافیکی (GPU) این دستورات ترسیم را از یک صف می‌خواند و آنها را پردازش می‌کند.

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

کلید کاهش این مشکل، کاهش پیچیدگی کارهایی است که روی پردازنده گرافیکی (GPU) انجام می‌شود، مشابه کاری که برای مرحله دستورات مشکل‌ساز انجام می‌دهید.

متفرقه

علاوه بر زمانی که سیستم رندرینگ برای انجام کار خود صرف می‌کند، مجموعه‌ای از کارهای اضافی نیز وجود دارد که در نخ اصلی رخ می‌دهد و هیچ ارتباطی با رندرینگ ندارد. زمانی که این کار صرف می‌کند به عنوان زمان متفرقه گزارش می‌شود. زمان متفرقه عموماً نشان‌دهنده کاری است که ممکن است بین دو فریم رندرینگ متوالی در نخ رابط کاربری رخ دهد.

وقتی این بخش بزرگ باشد

اگر این مقدار بالا باشد، احتمالاً برنامه شما دارای فراخوانی‌های برگشتی، اینتنت‌ها یا کارهای دیگری است که باید در ترد دیگری انجام شوند. ابزارهایی مانند CPU Profiler در اندروید استودیو یا Perfetto می‌توانند قابلیت مشاهده وظایفی که در ترد اصلی اجرا می‌شوند را فراهم کنند. این اطلاعات می‌تواند به شما در هدف قرار دادن بهبود عملکرد کمک کند. برای اطلاعات بیشتر به مرور کلی ردیابی سیستم مراجعه کنید.