RenderingNG غواصی عمیق: BlinkNG

استفان زاگر
Stefan Zager
کریس هارلسون
Chris Harrelson

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

Blink زندگی خود را به عنوان یک فورک WebKit آغاز کرد که خود یک فورک از KHTML است که به سال 1998 برمی‌گردد. این کد حاوی برخی از قدیمی‌ترین (و حیاتی‌ترین) کدهای Chromium است و تا سال 2014 قطعا سن خود را نشان می‌داد. در آن سال، ما مجموعه‌ای از پروژه‌های بلندپروازانه را تحت عنوان آنچه BlinkNG می‌نامیم، با هدف رفع کمبودهای طولانی مدت در سازمان و ساختار کد Blink آغاز کردیم. این مقاله BlinkNG و پروژه‌های تشکیل‌دهنده آن را بررسی می‌کند: چرا ما آنها را انجام دادیم، آن‌ها چه کاری انجام دادند، اصول راهنمایی که طراحی آن‌ها را شکل داد، و فرصت‌هایی برای پیشرفت‌های آینده که آن‌ها در اختیار دارند.

خط لوله رندر قبل و بعد از BlinkNG.

رندر قبل از NG

خط لوله رندر در Blink همیشه از نظر مفهومی به فازها ( سبک ، طرح ، رنگ و غیره) تقسیم می شد، اما موانع انتزاعی نشتی داشتند. به طور کلی، داده های مرتبط با رندر شامل اشیاء با عمر طولانی و قابل تغییر است. این اشیاء می‌توانستند – و می‌شدند – در هر زمان اصلاح شوند، و اغلب با به‌روزرسانی‌های رندر متوالی بازیافت و دوباره استفاده می‌شدند. پاسخ قابل اعتماد به سؤالات ساده ای مانند:

  • آیا خروجی سبک، چیدمان یا رنگ نیاز به به روز رسانی دارد؟
  • چه زمانی این داده ها ارزش "نهایی" خود را خواهند گرفت؟
  • چه زمانی اصلاح این داده ها درست است؟
  • چه زمانی این شی حذف می شود؟

نمونه های زیادی از این وجود دارد، از جمله:

Style بر اساس شیوه نامه ها ComputedStyle را تولید می کند. اما ComputedStyle تغییرناپذیر نبود. در برخی موارد با مراحل بعدی خط لوله اصلاح می شود.

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

Style ساختارهای داده جانبی را تولید می کند که مسیر ترکیب را تعیین می کند و آن ساختارهای داده در هر مرحله به سبک تغییر می کنند.

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

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

چیزی که ما تغییر دادیم

BlinkNG از بسیاری از پروژه های فرعی، بزرگ و کوچک تشکیل شده است که همگی با هدف مشترک حذف کاستی های معماری که قبلاً توضیح داده شد، هستند. این پروژه ها چند اصل راهنما را به اشتراک می گذارند که طراحی شده اند تا خط لوله رندرینگ را بیشتر به یک خط لوله واقعی تبدیل کنند:

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

فهرست کاملی از پروژه های فرعی BlinkNG برای خواندن خسته کننده خواهد بود، اما موارد زیر چند پیامد خاص هستند.

چرخه عمر سند

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

  • اگر یک ویژگی ComputedStyle را اصلاح می کنیم، چرخه عمر سند باید kInStyleRecalc باشد.
  • اگر حالت DocumentLifecycle kStyleClean یا جدیدتر باشد، NeedsStyleRecalc() باید برای هر گره متصل شده ، false را برگرداند.
  • هنگام ورود به مرحله چرخه عمر رنگ ، وضعیت چرخه حیات باید kPrePaintClean باشد.

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

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

  • همه داده‌های رندر باید به‌روزرسانی شوند - به عنوان مثال، هنگام تولید پیکسل‌های جدید برای نمایش یا انجام آزمایش ضربه برای هدف‌یابی رویداد.
  • ما به یک مقدار به روز برای یک پرس و جوی خاص نیاز داریم که می تواند بدون به روز رسانی تمام داده های رندر پاسخ داده شود. این شامل اکثر جستارهای جاوا اسکریپت، به عنوان مثال، node.offsetTop است.

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

سبک لوله کشی، چیدمان و پیش رنگ

در مجموع، مراحل رندر قبل از رنگ ، مسئولیت موارد زیر را بر عهده دارند:

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

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

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

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

در اینجا چند پروژه مهم وجود دارد که کاستی های معماری فازهای رندر قبل از رنگ آمیزی را از بین برده است.

تیم پروژه: خط لوله فاز سبک

این پروژه دو کسری اصلی در فاز سبک را برطرف کرد که از لوله کشی تمیز آن جلوگیری کرد:

دو خروجی اولیه از فاز سبک وجود دارد: ComputedStyle ، شامل نتیجه اجرای الگوریتم آبشار CSS بر روی درخت DOM. و درختی از LayoutObjects که ترتیب عملیات را برای مرحله طرح بندی تعیین می کند. از نظر مفهومی، اجرای الگوریتم آبشار باید دقیقاً قبل از تولید درخت طرح اتفاق بیفتد. اما قبلاً این دو عملیات به هم پیوسته بودند. Project Squad موفق شد این دو را به فازهای متمایز و متوالی تقسیم کند.

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

LayoutNG: لوله کشی مرحله طرح

این پروژه به یاد ماندنی - یکی از سنگ بنای RenderingNG - بازنویسی کامل مرحله رندر چیدمان بود. ما در اینجا کل پروژه را رعایت نمی کنیم، اما چند جنبه قابل توجه برای پروژه کلی BlinkNG وجود دارد:

  • قبلاً، مرحله طرح، درختی از LayoutObject را دریافت می‌کرد که توسط فاز سبک ایجاد شده بود، و درخت را با اطلاعات اندازه و موقعیت حاشیه‌نویسی می‌کرد. بنابراین، هیچ جداسازی تمیزی از ورودی ها از خروجی ها وجود نداشت. LayoutNG درخت قطعه را معرفی کرد که خروجی اولیه و فقط خواندنی layout است و به عنوان ورودی اولیه برای مراحل رندر بعدی عمل می کند.
  • LayoutNG خاصیت containment را به layout آورده است: هنگام محاسبه اندازه و موقعیت یک LayoutObject معین، دیگر به بیرون درخت فرعی که ریشه در آن شیء دارد نگاه نمی کنیم. تمام اطلاعات مورد نیاز برای به روز رسانی طرح برای یک شی معین از قبل محاسبه شده و به عنوان ورودی فقط خواندنی به الگوریتم ارائه می شود.
  • پیش از این، موارد لبه‌ای وجود داشت که الگوریتم طرح‌بندی کاملاً کاربردی نبود: نتیجه الگوریتم به آخرین به‌روزرسانی طرح‌بندی قبلی بستگی داشت. LayoutNG آن موارد را حذف کرد.

مرحله پیش رنگ

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

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

درختان دارایی: هندسه ثابت

درختان ویژگی در اوایل RenderingNG برای مقابله با پیچیدگی اسکرول معرفی شدند، که در وب ساختار متفاوتی نسبت به سایر انواع جلوه های بصری دارد. قبل از درخت‌های ویژگی، ترکیب‌کننده Chromium از یک سلسله‌مراتب "لایه" برای نمایش رابطه هندسی محتوای ترکیبی استفاده می‌کرد، اما با آشکار شدن پیچیدگی‌های کامل ویژگی‌هایی مانند موقعیت: ثابت، به سرعت از بین رفت. سلسله مراتب لایه نشانگرهای غیرمحلی اضافی را افزایش داد که «والد اسکرول» یا «والد کلیپ» یک لایه را نشان می‌دهد، و خیلی زود درک کد بسیار سخت بود.

Property trees این مشکل را با نمایش جنبه‌های اسکرول سرریز و کلیپ محتوا به‌طور جداگانه از تمام جلوه‌های بصری دیگر برطرف کردند. این امر امکان مدل سازی صحیح ساختار بصری و پیمایشی وب سایت ها را فراهم کرد. در مرحله بعد، «تمام کاری» که باید انجام می‌دادیم این بود که الگوریتم‌هایی را در بالای درخت‌های ویژگی پیاده‌سازی کنیم، مانند تبدیل فضای صفحه‌نمایش لایه‌های ترکیبی، یا تعیین اینکه کدام لایه‌ها اسکرول می‌شوند و کدام‌ها نه.

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

این الگوریتم‌ها به نوبه خود همه به درخت‌های ویژگی بستگی دارند، به همین دلیل است که درخت‌های ویژگی یک ساختار داده کلیدی هستند – یعنی ساختاری که در سراسر خط لوله RenderingNG استفاده می‌شود. بنابراین برای دستیابی به این هدف از کد هندسه متمرکز، ما نیاز داشتیم که مفهوم درختان ویژگی را خیلی زودتر در خط لوله معرفی کنیم - در pre-paint - و همه APIهایی را که اکنون به آنها وابسته بودند تغییر دهیم تا قبل از اجرای آنها نیاز به اجرای pre-paint داشته باشند. .

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

کامپوزیت پس از رنگ: لوله کشی رنگ و کامپوزیت

لایه‌بندی فرآیندی است که مشخص می‌کند کدام محتوای DOM به لایه ترکیبی خود می‌رود (که به نوبه خود یک بافت GPU را نشان می‌دهد). قبل از RenderingNG، لایه‌بندی قبل از رنگ اجرا می‌شد، نه بعد از آن (برای خط لوله فعلی اینجا را ببینید – به تغییر ترتیب توجه کنید). ابتدا تصمیم می‌گیریم که کدام بخش از DOM در کدام لایه ترکیبی قرار می‌گیرد و تنها پس از آن فهرست‌های نمایشی برای آن بافت‌ها ترسیم می‌کنیم. به طور طبیعی، تصمیم‌گیری‌ها به عواملی مانند اینکه کدام عناصر DOM متحرک یا اسکرول می‌شوند، یا تبدیل‌های سه بعدی دارند و کدام عناصر روی آن‌ها نقاشی می‌شوند، بستگی دارد.

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

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

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

برنامه ما این بود که به مرور زمان از شر تمام سایت های تماس، اشیاء DisableCompositingQueryAssert خلاص شویم و سپس کد را ایمن و صحیح اعلام کنیم. اما چیزی که ما کشف کردیم این بود که حذف تعدادی از تماس ها تا زمانی که لایه بندی قبل از رنگ اتفاق می افتاد اساساً غیرممکن بود. (نهایتاً اخیراً توانستیم آن را حذف کنیم!) این اولین دلیلی بود که برای پروژه Composite After Paint کشف شد. چیزی که ما آموختیم این بود که، حتی اگر یک فاز خط لوله به خوبی تعریف شده برای یک عملیات داشته باشید، اگر در جای اشتباهی در خط لوله باشد، در نهایت گیر خواهید کرد.

دلیل دوم پروژه Composite After Paint، باگ Fundamental Compositing بود. یک راه برای بیان این اشکال این است که عناصر DOM نمایش 1:1 خوبی از یک طرح لایه‌بندی کارآمد یا کامل برای محتویات صفحه وب نیستند. و از آنجایی که کامپوزیت قبل از رنگ بود، کم و بیش ذاتاً به عناصر DOM بستگی داشت، نه فهرست‌های نمایش یا درخت‌های ویژگی. این بسیار شبیه به دلیلی است که ما درخت‌های ویژگی را معرفی کردیم، و درست مانند درخت‌های ویژگی، اگر فاز خط لوله مناسب را پیدا کنید، آن را در زمان مناسب اجرا کنید و ساختار داده‌های کلیدی صحیح را برای آن ارائه کنید، راه‌حل مستقیماً از بین می‌رود. و مانند درختان دارایی، این فرصت خوبی برای تضمین این بود که پس از تکمیل فاز رنگ، خروجی آن برای تمام فازهای بعدی خط لوله تغییرناپذیر است.

فواید

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

  • قابلیت اطمینان بسیار بهبود یافته : این یکی بسیار ساده است. کد پاک‌تر با رابط‌های کاملاً تعریف‌شده و قابل فهم درک، نوشتن و آزمایش آسان‌تر است. این باعث اطمینان بیشتر آن می شود. همچنین کد را ایمن‌تر و پایدارتر می‌کند، با خرابی‌های کمتر و باگ‌های پس از استفاده کمتر.
  • پوشش آزمایشی گسترده : در دوره BlinkNG، تعداد زیادی تست جدید به مجموعه خود اضافه کردیم. این شامل تست‌های واحدی است که تأیید متمرکز داخلی‌ها را فراهم می‌کند. تست های رگرسیون که ما را از معرفی مجدد اشکالات قدیمی که رفع کرده ایم (بسیار زیاد!) باز می دارد. و تعداد زیادی افزودنی به مجموعه آزمایشی پلتفرم وب عمومی که به طور جمعی نگهداری می شوند، که همه مرورگرها از آن برای سنجش انطباق با استانداردهای وب استفاده می کنند.
  • توسعه آسانتر : اگر یک سیستم به اجزای واضح تقسیم شود، برای پیشرفت در سیستم فعلی، نیازی به درک سایر اجزا در هر سطحی از جزئیات نیست. این امر باعث می‌شود که همه بدون نیاز به متخصص بودن، ارزش افزودن به کد رندر را آسان‌تر کنند و همچنین استدلال در مورد رفتار کل سیستم را آسان‌تر می‌کند.
  • عملکرد : بهینه سازی الگوریتم های نوشته شده در کد اسپاگتی به اندازه کافی دشوار است، اما دستیابی به چیزهای بزرگتر مانند اسکرول رشته ای جهانی و انیمیشن ها یا فرآیندها و رشته ها برای جداسازی سایت بدون چنین خط لوله تقریباً غیرممکن است. موازی سازی می تواند به ما در بهبود عملکرد فوق العاده کمک کند، اما همچنین بسیار پیچیده است.
  • تسلیم و مهار : چندین ویژگی جدید توسط BlinkNG امکان پذیر شده است که خط لوله را به روش های جدید و جدید اعمال می کند. به عنوان مثال، اگر بخواهیم فقط خط لوله رندر را اجرا کنیم تا زمانی که بودجه منقضی شود، چه؟ یا از رندر برای زیردرخت هایی که در حال حاضر به کاربر مرتبط نیستند، صرفنظر کنید؟ این همان چیزی است که ویژگی CSS مشاهده محتوا را فعال می کند. در مورد اینکه سبک یک کامپوننت به چیدمان آن بستگی دارد چطور؟ این عبارت جستجوی کانتینر است.

مطالعه موردی: پرس و جوهای کانتینری

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

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

چگونه می توانیم این را حل کنیم؟ آیا این یک وابستگی خط لوله به عقب نیست، یعنی همان مشکلی که پروژه هایی مانند Composite After Paint حل کردند؟ حتی بدتر از آن، اگر سبک های جدید اندازه اجداد را تغییر دهند چه؟ آیا این گاهی اوقات به یک حلقه بی نهایت منجر نمی شود؟

در اصل، وابستگی دایره ای را می توان با استفاده از ویژگی Contain CSS حل کرد، که اجازه می دهد رندر خارج از یک عنصر به رندر در زیر درخت آن عنصر وابسته نباشد . این بدان معناست که سبک‌های جدید اعمال‌شده توسط یک کانتینر نمی‌تواند بر اندازه ظرف تأثیر بگذارد، زیرا درخواست‌های کانتینر به محدودیت نیاز دارند .

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

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

آینده: ترکیب خارج از موضوع اصلی … و فراتر از آن!

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

برای درک اینکه چرا این مهم است و به کجاهای دیگری ممکن است منجر شود، باید معماری موتور رندر را از نقطه نظر تا حدودی بالاتر در نظر بگیریم. یکی از بادوام ترین موانع برای بهبود عملکرد Chromium این واقعیت ساده است که رشته اصلی رندر هم منطق برنامه اصلی (یعنی اجرای اسکریپت) و هم بخش عمده رندر را کنترل می کند. در نتیجه، رشته اصلی اغلب از کار اشباع می شود و تراکم نخ اصلی اغلب گلوگاه در کل مرورگر است.

خبر خوب این است که لازم نیست اینگونه باشد! قدمت این جنبه از معماری Chromium به روزهای KHTML برمی‌گردد، زمانی که اجرای تک رشته‌ای مدل غالب برنامه‌نویسی بود. زمانی که پردازنده‌های چند هسته‌ای در دستگاه‌های درجه یک مصرف‌کننده رایج شدند، فرض تک رشته‌ای به طور کامل در Blink (که قبلاً WebKit بود) ساخته شد. مدت‌هاست که می‌خواستیم threading بیشتری را به موتور رندر وارد کنیم، اما در سیستم قدیمی غیرممکن بود. یکی از اهداف اصلی Rendering NG این بود که خود را از این حفره بیرون بیاوریم و امکان انتقال کار رندر، به طور جزئی یا کلی، به رشته (یا نخ) دیگر را فراهم کنیم.

اکنون که BlinkNG رو به اتمام است، ما در حال شروع به کشف این منطقه هستیم. Non-Blocking Commit اولین تلاش برای تغییر مدل threading رندر است. Compositor commit (یا فقط commit ) یک مرحله همگام سازی بین رشته اصلی و رشته کامپوزیتور است. در طول commit، ما از داده‌های رندری که در رشته اصلی تولید می‌شوند، کپی می‌سازیم تا توسط کد ترکیبی پایین‌دستی که روی رشته کامپوزیتور اجرا می‌شود، استفاده شود. در حالی که این همگام سازی اتفاق می افتد، اجرای رشته اصلی متوقف می شود در حالی که کد کپی بر روی رشته کامپوزیتور اجرا می شود. این کار برای اطمینان از اینکه رشته اصلی داده های رندر خود را تغییر نمی دهد در حالی که رشته ترکیب کننده آن را کپی می کند انجام می شود.

Non-Blocking Commit نیاز به توقف رشته اصلی و صبر کردن برای پایان مرحله commit را از بین می‌برد - رشته اصلی به کار خود ادامه می‌دهد در حالی که commit به طور همزمان بر روی رشته ترکیب اجرا می‌شود. اثر خالص Non-Blocking Commit کاهش زمان اختصاص داده شده به رندر کار بر روی نخ اصلی خواهد بود که باعث کاهش تراکم روی رشته اصلی و بهبود عملکرد می شود. از زمان نگارش این مقاله (مارس 2022)، ما یک نمونه اولیه از Non-Blocking Commit داریم و در حال آماده شدن برای انجام تجزیه و تحلیل دقیق تأثیر آن بر عملکرد هستیم.

Waiting in the wings Compositing Off-Main-thread است، با هدف ایجاد مطابقت موتور رندر با تصویر با جابجایی لایه‌بندی از نخ اصلی و روی یک نخ کارگر. مانند Non-Blocking Commit، این کار با کاهش حجم کاری رندر، تراکم روی رشته اصلی را کاهش می‌دهد. پروژه ای مانند این هرگز بدون پیشرفت های معماری Composite After Paint امکان پذیر نبود.

و پروژه های بیشتری در خط لوله وجود دارد ( جناس در نظر گرفته شده )! ما در نهایت پایه‌ای داریم که امکان آزمایش با توزیع مجدد کار رندر را ممکن می‌سازد، و ما بسیار هیجان‌زده هستیم که ببینیم چه چیزی ممکن است!