نمایه کردن بازی WebGL خود با پرچم about:tracing

لیلی تامپسون
Lilli Thompson

اگر نتوانید آن را اندازه گیری کنید، نمی توانید آن را بهبود ببخشید.

لرد کلوین

برای اینکه بازی های HTML5 خود را سریعتر اجرا کنید، ابتدا باید گلوگاه های عملکرد را مشخص کنید، اما این می تواند دشوار باشد. ارزیابی داده‌های فریم در ثانیه (FPS) یک شروع است، اما برای دیدن تصویر کامل، باید تفاوت‌های ظریف در فعالیت‌های Chrome را درک کنید.

ابزار about:tracing بینشی را ارائه می‌کند که به شما کمک می‌کند از راه‌حل‌های عجولانه با هدف بهبود عملکرد اجتناب کنید، اما اساساً حدس‌هایی با نیت خوب هستند. شما در زمان و انرژی زیادی صرفه جویی خواهید کرد، تصویر واضح تری از کاری که Chrome با هر فریم انجام می دهد به دست خواهید آورد و از این اطلاعات برای بهینه سازی بازی خود استفاده خواهید کرد.

سلام about:tracing

ابزار about:tracing کروم پنجره‌ای به تمام فعالیت‌های کروم در یک دوره زمانی با جزئیات بسیار زیاد در اختیار شما قرار می‌دهد که ممکن است در ابتدا آن را طاقت‌فرسا بدانید. بسیاری از عملکردهای کروم برای ردیابی خارج از جعبه ابزارسازی شده‌اند، بنابراین بدون انجام هیچ ابزار دقیق دستی، همچنان می‌توانید about:tracing برای ردیابی عملکرد خود استفاده کنید. (بخش بعدی در مورد ابزاردهی دستی JS خود را ببینید)

برای مشاهده نمای ردیابی، کافی است «about:tracing» را در omnibox Chrome (نوار آدرس) تایپ کنید.

Chrome omnibox
«about:tracing» را در omnibox Chrome تایپ کنید

از ابزار ردیابی، می توانید شروع به ضبط کنید، بازی خود را برای چند ثانیه اجرا کنید و سپس داده های ردیابی را مشاهده کنید. این نمونه ای از این است که داده ها ممکن است به نظر برسند:

نتیجه ردیابی ساده
نتیجه ردیابی ساده

بله، این گیج کننده است. بیایید در مورد نحوه خواندن آن صحبت کنیم.

هر ردیف نمایانگر فرآیندی است که نمایه می شود، محور چپ به راست زمان را نشان می دهد و هر کادر رنگی یک فراخوانی تابع ابزاری است. ردیف هایی برای تعدادی از انواع مختلف منابع وجود دارد. مواردی که بیشتر برای پروفایل بازی جالب هستند CrGpuMain هستند که نشان می دهد واحد پردازش گرافیک (GPU) چه می کند و CrRendererMain. هر ردیابی شامل خطوط CrRendererMain برای هر برگه باز در طول دوره ردیابی (از جمله خود برگه about:tracing ) است.

هنگام خواندن داده های ردیابی اولین وظیفه شما این است که تعیین کنید کدام ردیف CrRendererMain با بازی شما مطابقت دارد.

نتیجه ردیابی ساده برجسته شده است
نتیجه ردیابی ساده برجسته شده است

در این مثال، دو نامزد عبارتند از: 2216 و 6516. متأسفانه در حال حاضر راهی برای انتخاب برنامه شما وجود ندارد، جز اینکه به دنبال خطی بگردید که به‌روزرسانی‌های دوره‌ای زیادی را انجام می‌دهد (یا اگر به صورت دستی کد خود را با آن تنظیم کرده‌اید. نقاط ردیابی، برای جستجوی خطی که حاوی داده های ردیابی شما است). در این مثال، به نظر می رسد 6516 یک حلقه اصلی را از فرکانس به روز رسانی ها اجرا می کند. اگر همه تب های دیگر را قبل از شروع ردیابی ببندید، پیدا کردن CrRendererMain صحیح آسان تر خواهد بود. اما همچنان ممکن است ردیف‌های CrRendererMain برای فرآیندهای غیر از بازی شما وجود داشته باشد.

پیدا کردن قاب شما

هنگامی که ردیف صحیح را در ابزار ردیابی بازی خود پیدا کردید، گام بعدی یافتن حلقه اصلی است. حلقه اصلی مانند یک الگوی تکرار شونده در داده های ردیابی به نظر می رسد. می‌توانید داده‌های ردیابی را با استفاده از کلیدهای W، A، S، D پیمایش کنید: A و D برای حرکت به چپ یا راست (در زمان به جلو و عقب) و W و S برای بزرگ‌نمایی و کوچک‌نمایی روی داده‌ها. اگر بازی شما با فرکانس 60 هرتز اجرا می شود، انتظار دارید حلقه اصلی شما الگویی باشد که هر 16 میلی ثانیه تکرار شود.

به نظر می رسد سه فریم اجرایی است
به نظر می رسد سه فریم اجرایی است

هنگامی که ضربان قلب بازی خود را مشخص کردید، می‌توانید دقیقاً در هر فریم کدتان را بررسی کنید. از W، A، S، D برای بزرگنمایی استفاده کنید تا زمانی که بتوانید متن را در کادرهای تابع بخوانید.

در اعماق یک قاب اجرا
در اعماق یک قاب اجرا

این مجموعه از جعبه ها یک سری فراخوانی تابع را نشان می دهد که هر فراخوانی با یک کادر رنگی نمایش داده می شود. هر تابع توسط کادر بالای آن فراخوانی می‌شود، بنابراین در این مورد، می‌توانید MessageLoop::RunTask به نام RenderWidget::OnSwapBuffersComplete را ببینید که به نوبه خود RenderWidget::DoDeferredUpdate و غیره نامیده می‌شود. با خواندن این داده ها، می توانید دید کاملی از آنچه که هر اجرا چیست و چه مدت طول می کشد را دریافت کنید.

اما اینجاست که کمی چسبناک می شود. اطلاعات افشا شده توسط about:tracing فراخوانی های تابع خام از کد منبع کروم است. می‌توانید از روی نام، حدس‌های درستی در مورد اینکه هر عملکرد چه می‌کند بکنید، اما اطلاعات دقیقاً کاربرپسند نیست. دیدن جریان کلی کادرتان مفید است، اما برای اینکه بفهمید چه اتفاقی در حال رخ دادن است، به چیزی کمی خواناتر نیاز دارید.

افزودن تگ های ردیابی

خوشبختانه روشی دوستانه برای افزودن ابزار دقیق به کد خود برای ایجاد داده های ردیابی وجود دارد: console.time و console.timeEnd .

console.time("update");
update
();
console
.timeEnd("update");
console
.time("render");
update
();
console
.timeEnd("render");

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

برچسب ها به صورت دستی اضافه شدند
برچسب ها به صورت دستی اضافه شدند

با استفاده از این، می توانید داده های ردیابی قابل خواندن برای انسان برای ردیابی نقاط مهم در کد خود ایجاد کنید.

GPU یا CPU؟

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

ابتدا خطی را در نمای ردیابی به نام CrGPUMain بیابید که نشان می دهد پردازنده گرافیکی در یک زمان خاص مشغول است یا خیر.

ردیابی GPU و CPU

می بینید که هر فریم از بازی شما باعث کار CPU در CrRendererMain و همچنین روی GPU می شود. ردیابی بالا یک مورد استفاده بسیار ساده را نشان می دهد که در آن هر دو CPU و GPU برای اکثر فریم های 16 میلی ثانیه بیکار هستند.

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

console.time("update");
doExtraWork
();
update
(Math.min(50, now - time));
console
.timeEnd("update");

console
.time("render");
render
();
console
.timeEnd("render");

اکنون ردی را خواهید دید که به شکل زیر است:

ردیابی GPU و CPU

این ردیابی به ما چه می گوید؟ می‌توانیم ببینیم که فریم تصویر از حدود 2270 میلی‌ثانیه به 2320 میلی‌ثانیه می‌رسد، به این معنی که هر فریم حدود 50 میلی‌ثانیه (با نرخ فریم 20 هرتز) می‌گیرد. می‌توانید تکه‌هایی از جعبه‌های رنگی را ببینید که عملکرد رندر را در کنار جعبه به‌روزرسانی نشان می‌دهند، اما فریم کاملاً تحت تسلط خود به‌روزرسانی است.

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

وقتی خود کد سایه زن کند است و GPU بیش از حد کار می کند چطور؟ اگر کارهای غیر ضروری را از CPU حذف کنیم و به جای آن مقداری کار در کد shader fragment اضافه کنیم چه می شود. در اینجا یک قطعه سایه زن بیهوده گران قیمت وجود دارد:

#ifdef GL_ES
precision highp
float;
#endif
void main(void) {
 
for(int i=0; i<9999; i++) {
    gl_FragColor
= vec4(1.0, 0, 0, 1.0);
 
}
}

ردی از کد با استفاده از آن سایه زن چگونه به نظر می رسد؟

هنگام استفاده از کدهای کند پردازنده گرافیکی و پردازنده گرافیکی ردیابی می شود
هنگام استفاده از کدهای کند پردازنده گرافیکی و پردازنده گرافیکی ردیابی می شود

باز هم به مدت زمان یک فریم توجه کنید. در اینجا الگوی تکرار از حدود 2750 میلی‌ثانیه به 2950 میلی‌ثانیه، مدت زمان 200 میلی‌ثانیه (نرخ فریم حدود 5 هرتز) می‌رسد. خط CrRendererMain تقریباً کاملاً خالی است به این معنی که CPU بیشتر اوقات بیکار است، در حالی که GPU بیش از حد بارگذاری شده است. این یک علامت مطمئن است که شیدرهای شما بیش از حد سنگین هستند.

اگر دقیقاً متوجه نشدید که دقیقاً چه چیزی باعث نرخ فریم پایین شده است، می توانید به روز رسانی 5 هرتز را مشاهده کنید و وسوسه شوید که وارد کد بازی شوید و شروع به تلاش برای بهینه سازی یا حذف منطق بازی کنید. در این مورد، این هیچ فایده ای ندارد، زیرا منطق در حلقه بازی چیزی نیست که زمان را از بین ببرد. در واقع، آنچه این ردیابی نشان می‌دهد این است که انجام کار بیشتر با CPU در هر فریم اساساً «رایگان» خواهد بود، زیرا CPU در اطراف بی‌حرکت آویزان است، بنابراین کار بیشتر آن تأثیری بر طول مدت زمان فریم نخواهد داشت.

نمونه های واقعی

حالا بیایید بررسی کنیم که داده های ردیابی از یک بازی واقعی چگونه به نظر می رسد. یکی از چیزهای جالب در مورد بازی های ساخته شده با فناوری های وب باز این است که می توانید ببینید در محصولات مورد علاقه خود چه می گذرد. اگر می‌خواهید ابزارهای نمایه‌سازی را آزمایش کنید، می‌توانید عنوان WebGL مورد علاقه خود را از فروشگاه وب Chrome انتخاب کنید و آن را با about:tracing نمایه کنید. این یک نمونه ردیابی است که از بازی عالی WebGL Skid Racer گرفته شده است.

ردیابی یک بازی واقعی
ردیابی یک بازی واقعی

به نظر می رسد هر فریم حدود 20 میلی ثانیه طول می کشد، به این معنی که نرخ فریم حدود 50 فریم در ثانیه است. می بینید که کار بین CPU و GPU متعادل است، اما GPU منبعی است که بیشترین تقاضا را دارد. اگر می‌خواهید ببینید نمایه کردن نمونه‌های واقعی از بازی‌های WebGL چگونه است، سعی کنید با برخی از عناوین فروشگاه وب Chrome ساخته شده با WebGL از جمله:

نتیجه گیری

اگر می‌خواهید بازی شما با فرکانس 60 هرتز اجرا شود، برای هر فریم، تمام عملیات شما باید در 16 میلی‌ثانیه CPU و 16 میلی‌ثانیه زمان پردازشگر گرافیکی قرار گیرد. شما دو منبع دارید که می‌توان از آنها به صورت موازی استفاده کرد و می‌توانید کار را بین آنها تغییر دهید تا عملکرد را به حداکثر برسانید. about:tracing Chrome ابزاری ارزشمند برای دریافت بینش در مورد آنچه کد شما واقعاً انجام می‌دهد است و به شما کمک می‌کند زمان توسعه خود را با رفع مشکلات درست به حداکثر برسانید.

بعدش چی؟

علاوه بر GPU، می‌توانید بخش‌های دیگری از زمان اجرا کروم را نیز ردیابی کنید. Chrome Canary، نسخه اولیه کروم، برای ردیابی IO، IndexedDB و چندین فعالیت دیگر استفاده می‌شود. برای درک عمیق تر از وضعیت فعلی ردیابی رویدادها، باید این مقاله Chromium را بخوانید.

اگر توسعه دهنده بازی های وب هستید، حتما ویدیوی زیر را تماشا کنید. این ارائه‌ای از تیم مدافع توسعه‌دهنده بازی Google در GDC 2012 درباره بهینه‌سازی عملکرد بازی‌های کروم است: