بهبود عملکرد دسترسی Chromium

این پست از مشارکت‌کننده Chromium احمد الواصفی است که نحوه مشارکت‌کننده شدنش از طریق Google Summer of Code و مشکلات عملکرد دسترسی که شناسایی و رفع کرده است را به اشتراک می‌گذارد.

با نزدیک شدن به آخرین سال تحصیلی خود در رشته مهندسی کامپیوتر در دانشگاه آلمان در قاهره، تصمیم گرفتم فرصت هایی را برای کمک به منبع باز کشف کنم. من شروع به کاوش در فهرست مشکلات مبتدی Chromium کردم و متوجه شدم که به ویژه به سمت دسترسی جذب شده ام. جستجوی من برای راهنمایی، مرا به آرون لونتال رساند، که تخصص و تمایل او به کمک، الهام بخش من شد تا با او برای یک پروژه همکاری کنم. این همکاری به تجربه Google Summer of Code من تبدیل شد، جایی که من برای کار با تیم دسترسی Chromium پذیرفته شدم.

پس از تکمیل موفقیت آمیز Google Summer of Code، من همچنان به یک مشکل پیمایش حل نشده پرداختم که ناشی از تمایل به بهبود عملکرد بود. به لطف دو کمک مالی از برنامه OpenCollective Google، من توانستم به کار بر روی این پروژه ادامه دهم و در عین حال وظایف دیگری را نیز بر عهده بگیرم که بر ساده کردن کد برای عملکرد بهتر تمرکز دارد.

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

چگونه کد دسترس‌پذیری بر عملکرد در Chrome تأثیر می‌گذارد

کد دسترس‌پذیری Chrome به فناوری‌های کمکی مانند صفحه‌خوان‌ها برای دسترسی به وب کمک می‌کند. با این حال، وقتی فعال باشد، می‌تواند بر زمان بارگذاری، عملکرد و عمر باتری تأثیر بگذارد. بنابراین، در صورت عدم نیاز، برای جلوگیری از کاهش سرعت عملکرد، این کد غیرفعال می‌ماند. تقریباً 5 تا 10 درصد از کاربران کد دسترسی را فعال کرده‌اند که اغلب به دلیل ابزارهایی مانند مدیریت رمز عبور و نرم‌افزار آنتی ویروس است که از APIهای دسترسی پلت فرم استفاده می‌کنند. این ابزارها برای تعامل و تغییر محتوای صفحه، مانند مکان یابی فیلدهای رمز عبور برای مدیران رمز عبور و پرکننده‌های فرم، به این APIها متکی هستند.

تنزل کل در معیارهای اصلی هنوز مشخص نیست، اما آزمایش اخیر به نام Auto Disable Accessibility (خاموش کردن دسترسی در صورت عدم استفاده)، نشان می‌دهد که بسیار زیاد است. این مشکل به دلیل حجم انبوه محاسبات و ارتباطات در دو حوزه اصلی پایگاه کد دسترسی کروم رخ می دهد: رندر و مرورگر. رندر کننده اطلاعات مربوط به محتویات وب و تغییرات در محتوا را جمع آوری می کند و ویژگی های دسترسی را برای درختی از گره ها محاسبه می کند. سپس هر گره کثیفی سریال می شود و از طریق لوله ای به رشته اصلی UI فرآیند مرورگر ارسال می شود. این رشته این اطلاعات را به درخت یکسان گره ها دریافت و از سریال خارج می کند و در نهایت آن را به فرم مناسبی برای فناوری های کمکی شخص ثالث مانند صفحه خوان ها تبدیل می کند.

بهبودهایی در دسترسی Chromium

پروژه های زیر در طول تابستان کد و سپس پس از آن، توسط برنامه Google OpenCollective تکمیل شد.

بهبود حافظه پنهان

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

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

برای بهبود این امر، ما به سیستمی با استفاده از enums سوئیچ کردیم. به هر کار یک مقدار enum اختصاص داده می شود، و هنگامی که درخت دسترسی آماده شد، روش صحیح برای آن کار فراخوانی می شود. این تغییر درک کد را آسان‌تر کرد و عملکرد را تا 20 درصد بهبود بخشید.

نمودارهای تست عملکرد زمان اجرا
نموداری از زمان اجرا چندین آزمایش عملکرد که در آن افت قابل مشاهده حدود 20 درصد در همه آنها وجود دارد.

پیدا کردن و رفع مشکلات عملکرد اسکرول

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

برای آزمایش این، ما به طور موقت کدی را که جعبه‌های محدودکننده را کنترل می‌کند حذف کردیم و آزمایش‌های عملکرد را برای مشاهده تأثیر آن اجرا کردیم. یک آزمایش، focus-links.html پیشرفت عظیمی در حدود 1618٪ نشان داد. این کشف پایه و اساس کار بیشتر شد.

بررسی تست کند

من شروع به بررسی کردم که چرا آن تست خاص با جعبه‌های مرزبندی کند بود. تنها کاری که آزمایش انجام داد این بود که بر روی چندین لینک یکی پس از دیگری تمرکز کرد. بنابراین، موضوع اصلی باید یا تمرکز بر عناصر یا اسکرول است که با عمل تمرکز اتفاق افتاده است. برای آزمایش این، من {preventScroll: true} را به فراخوانی focus() در تست عملکرد اضافه کردم و اسکرول را متوقف کردم.

با غیرفعال شدن پیمایش، زمانی که محاسبات جعبه محدود فعال بودند، زمان تست به 1.2 میلی ثانیه کاهش یافت. این نشان داد که پیمایش مشکل واقعی است.

نتایج آزمون با غیرفعال کردن پیمایش.
زمانی که پیمایش غیرفعال می شود یا سریال سازی جعبه محدود حذف می شود، زمان اجرای آزمایش پیوندهای فوکوس از 20 میلی ثانیه به 1.1 میلی ثانیه کاهش می یابد.

من یک آزمایش جدید به نام scroll-in-page.html ایجاد کردم تا تست پیوندهای تمرکز را تکرار کند، اما به جای استفاده از فوکوس، با scrollIntoView() در میان عناصر پیمایش می کند. من پیمایش صاف و فوری را با و بدون محاسبات جعبه مرزی آزمایش کردم.

نتایج آزمون برای آزمون جدید.
زمان لازم برای پردازش اسکرول ها در پیمایش فوری 65 میلی ثانیه است در حالی که پیمایش صاف 123 میلی ثانیه طول می کشد.

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

ما قضیه را می دانستیم، اما چرا این اتفاق می افتاد؟

اکنون فهمیده بودیم که پیمایش منشا کندی زیادی در سریال‌سازی دسترسی است، اما هنوز باید دلیل آن را پیدا می‌کردیم. برای تجزیه و تحلیل این، از دو ابزار به نام‌های perf و pprof برای تجزیه و تحلیل کارهای انجام شده در فرآیند مرورگر استفاده شد. این ابزارها اغلب در C++ برای پروفایل استفاده می شوند. نمودارهای زیر گزیده ای از قسمت جالب را نشان می دهد.

نمودار تست های اسکرول پروفیل.
نموداری که از پروفایل تست های پیمایشی ایجاد شده است. نشان می دهد که زمان بیشتر برای فراخوانی یک تابع به نام Unserialize و همچنین به نام IsChildOfLeaf صرف می شود.

پس از بررسی، متوجه شدیم که مشکل خود کد deserialization نیست، بلکه فراوانی تماس‌ها با آن است. برای درک این موضوع، باید به نحوه عملکرد به‌روزرسانی‌های دسترس‌پذیری در Chromium نگاه کنیم. به روز رسانی ها به صورت جداگانه ارسال نمی شوند. در عوض، یک مکان مرکزی به نام AXObjectCache وجود دارد که تمام ویژگی ها را ذخیره می کند. هنگامی که یک گره تغییر می کند، روش های مختلف به حافظه پنهان اطلاع می دهند تا آن گره را برای سریال سازی بعدی به عنوان کثیف علامت گذاری کند. سپس، تمام ویژگی‌های یادداشت‌های کثیف، از جمله ویژگی‌های بدون تغییر، سریال‌سازی شده و به مرورگر ارسال می‌شوند. در حالی که این طراحی با داشتن یک مسیر به‌روزرسانی واحد، کد را ساده می‌کند و پیچیدگی را کاهش می‌دهد، زمانی که رویدادهای سریع «علامت‌گذاری به‌عنوان کثیف» مانند رویدادهای پیمایش وجود دارد، کند می‌شود. تنها چیزی که تغییر می کند مقادیر scrollX و scrollY است. با این حال، ما هر بار بقیه خواص را با آنها سریال می کنیم. نرخ به روز رسانی در اینجا به بیش از بیست بار در ثانیه رسیده است!

سریال‌سازی Bounding Box با استفاده از یک مسیر سریال‌سازی سریع‌تر که فقط جزئیات جعبه مرزی را ارسال می‌کند، این مشکل را برطرف می‌کند و اجازه می‌دهد به‌روزرسانی‌های سریع بدون تأثیرگذاری بر ویژگی‌های دیگر انجام شود. این روش به طور موثر تغییرات جعبه محدود را کنترل می کند.

رفع اسکرول

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

ساده سازی کدها

در این دوره، من روی کیفیت کد به عنوان بخشی از پروژه‌ای به نام Onion Soup تمرکز کردم که کد را با کاهش یا حذف کدهای غیرضروری در لایه‌ها ساده می‌کند.

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

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

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

نتیجه گیری

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

نتایج عملکرد چشمگیر بوده است. به عنوان مثال، تغییر استفاده از enums برای زمان‌بندی وظایف، عملکرد را بیش از 20% بهبود بخشید. علاوه بر این، اصلاح اسکرول ما منجر به کاهش 825٪ در تست‌های پیمایش شد. تغییرات ساده سازی کد نه تنها کد را واضح تر و قابل نگهداری تر کرده است، بلکه راه را برای پیشرفت های آینده هموار کرده است.

می‌خواهم از استفان زاگر، کریس هارلسون و میسون فرید برای حمایت و راهنمایی‌شان در طول سال، و به‌ویژه از آرون لونتال، که بدون او این فرصت ممکن نبود، تشکر کنم. همچنین می‌خواهم از Tab Atkins-Bittner و تیم GSoC برای حمایت‌شان تشکر کنم.

برای کسانی که به دنبال مشارکت در یک پروژه معنادار و توسعه مهارت های خود هستند، به شدت توصیه می کنم با Chromium درگیر شوند. این یک راه عالی برای یادگیری است، و برنامه‌هایی مانند Google Summer of Code نقطه شروع عالی برای سفر شما هستند.