محک زدن عملکرد CSS @property

تاریخ انتشار: 2 اکتبر 2024

هنگامی که شروع به استفاده از یک ویژگی جدید CSS می کنید، مهم است که تأثیر آن را بر عملکرد وب سایت های خود، چه مثبت یا منفی، درک کنید. با داشتن @property اکنون در Baseline، این پست تأثیر عملکرد آن را بررسی می‌کند، و کارهایی که می‌توانید برای جلوگیری از تأثیر منفی انجام دهید.

محک زدن عملکرد CSS با PerfTestRunner

برای محک زدن عملکرد CSS، مجموعه آزمایشی "CSS Selector Benchmark" را ساختیم. این توسط PerfTestRunner Chromium ارائه می شود و تأثیر عملکرد CSS را معیار قرار می دهد. این PerfTestRunner همان چیزی است که Blink–موتور رندر اصلی Chromium– از آن برای آزمایش‌های عملکرد داخلی خود استفاده می‌کند.

رانر شامل یک روش measureRunsPerSecond است که برای تست ها استفاده می شود. هرچه تعداد دویدن در ثانیه بیشتر باشد بهتر است. یک معیار اساسی measureRunsPerSecond با این کتابخانه به شکل زیر است:

const testResults = PerfTestRunner.measureRunsPerSecond({
  "Test Description",
  iterationCount: 5,
  bootstrap: function() {
    // Code to execute before all iterations run
    // For example, you can inject a style sheet here
  },
  setup: function() {
    // Code to execute before a single iteration
  },
  run: function() {
    // The actual test that gets run and measured.
    // A typical test adjusts something on the page causing a style or layout invalidation
  },
  tearDown: function() {
    // Code to execute after a single iteration has finished
    // For example, undo DOM adjustments made within run()
  },
  done: function() {
    // Code to be run after all iterations have finished.
    // For example, remove the style sheets that were injected in the bootstrap phase
  },
});

هر یک از گزینه‌ها برای measureRunsPerSecond از طریق نظرات در بلوک کد توضیح داده می‌شود و تابع run بخش اصلی است که اندازه‌گیری می‌شود.

معیارهای انتخابگر CSS به درخت DOM نیاز دارند

از آنجا که عملکرد انتخابگرهای CSS نیز به اندازه DOM بستگی دارد، این معیارها به یک درخت DOM با اندازه مناسب نیاز دارند. به جای ایجاد دستی این درخت DOM، این درخت تولید می شود.

به عنوان مثال، تابع makeTree زیر بخشی از معیارهای @property است. درختی متشکل از 1000 عنصر می سازد که در هر عنصر چند کودک تو در تو قرار دارند.

const $container = document.querySelector('#container');

function makeTree(parentEl, numSiblings) {
  for (var i = 0; i <= numSiblings; i++) {
    $container.appendChild(
      createElement('div', {
        className: `tagDiv wrap${i}`,
        innerHTML: `<div class="tagDiv layer1" data-div="layer1">
          <div class="tagDiv layer2">
            <ul class="tagUl">
              <li class="tagLi"><b class="tagB"><a href="/" class="tagA link" data-select="link">Select</a></b></li>
            </ul>
          </div>
        </div>`,
      })
    );
  }
}

makeTree($container, 1000);

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

اجرای یک معیار

برای اجرای یک معیار که بخشی از مجموعه آزمایشی است، ابتدا باید یک وب سرور راه اندازی کنید:

npm run start

پس از شروع، می توانید از معیار در URL منتشر شده آن بازدید کرده و window.startTest() را به صورت دستی اجرا کنید.

برای اجرای این معیارها به صورت مجزا - بدون هیچ گونه برنامه افزودنی یا عوامل دیگری - Puppeteer از CLI فعال می شود تا معیار تصویب شده را بارگیری و اجرا کند.

برای این معیارهای @property به طور خاص به جای بازدید از صفحه مربوطه در URL آن http://localhost:3000/benchmarks/at-rule/at-property.html دستورات زیر را در CLI فراخوانی کنید:

npm run benchmark at-rule/at-property

این صفحه را از طریق Puppeteer بارگیری می کند، به طور خودکار window.startTest() را فراخوانی می کند و نتایج را گزارش می دهد.

محک زدن عملکرد خصوصیات CSS

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

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

هنگام انجام این کار، بین ویژگی‌های CSS که ارث می‌برند و ویژگی‌های CSS که ارث نمی‌برند، باید تفاوت قائل شد.

  • هنگامی که یک ویژگی CSS که به ارث می رسد در یک عنصر هدف تغییر می کند، سبک های بالقوه همه عناصر در زیر درخت زیر عنصر هدف نیز باید تغییر کنند.
  • هنگامی که یک ویژگی CSS که به ارث نمی رسد در یک عنصر هدف تغییر می کند، فقط سبک های آن عنصر منفرد باطل می شوند.

از آنجایی که مقایسه خواصی که ارث می‌برند با ویژگی‌هایی که ارث نمی‌برند منصفانه نیست، دو مجموعه معیار برای اجرا وجود دارد:

  • مجموعه ای از معیارها با ویژگی هایی که به ارث می رسند.
  • مجموعه‌ای از معیارها با ویژگی‌هایی که ارث نمی‌برند.

مهم است که با دقت انتخاب کنید کدام ویژگی ها را معیار قرار دهید. در حالی که برخی از ویژگی‌ها (مانند accent-color ) فقط سبک‌ها را باطل می‌کنند، ویژگی‌های زیادی (مانند writing-mode ) وجود دارند که موارد دیگر مانند طرح‌بندی یا رنگ را نیز باطل می‌کنند. شما ویژگی هایی را می خواهید که فقط سبک ها را باطل می کند.

برای تعیین این موضوع، موارد را در لیست خصوصیات CSS Blink جستجو کنید. هر ویژگی دارای یک فیلد invalidate است که مواردی که باطل می شوند را فهرست می کند.

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

ارزیابی عملکرد ویژگی‌های CSS که به ارث می‌برند

اولین مجموعه از معیارها بر ویژگی‌های CSS که به ارث می‌برند تمرکز دارد. سه نوع ویژگی وجود دارد که برای آزمایش و مقایسه با یکدیگر به ارث می رسند:

  • یک ویژگی معمولی که به ارث می رسد: accent-color .
  • یک ویژگی سفارشی ثبت نشده: --unregistered .
  • یک دارایی سفارشی که با inherits: true : --registered .

ویژگی های سفارشی ثبت نشده به این لیست اضافه می شوند زیرا به طور پیش فرض ارث می برند.

همانطور که قبلا ذکر شد، ویژگی‌هایی که به ارث می‌برند با دقت انتخاب شده‌اند، به طوری که فقط سبک‌ها را باطل می‌کند و ویژگی‌هایی که به‌عنوان independent علامت‌گذاری نمی‌شوند.

در مورد ویژگی‌های سفارشی ثبت‌شده، فقط آنهایی که توصیفگر inherits بر روی true تنظیم شده‌اند در این اجرا آزمایش می‌شوند. توصیفگر inherits تعیین می کند که آیا دارایی به فرزندان ارث می برد یا خیر. مهم نیست که این ویژگی از طریق CSS @property یا JavaScript CSS.registerProperty ثبت شده باشد، زیرا ثبت خود بخشی از معیار نیست.

معیارها

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

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

پس از انجام یک بنچمارک، هر سبک تزریقی بازنشانی می‌شود تا معیار بعدی شروع شود.

به عنوان مثال، معیار سنجش عملکرد تغییر سبک --registered به این صورت است:

let i = 0;
PerfTestRunner.measureRunsPerSecond({
  description,
  iterationCount: 5,
  bootstrap: () => {
    setCSS(`@property --registered {
      syntax: "<number>";
      initial-value: 0;
      inherits: true;
    }`);
  },
  setup: function() {
    // NO-OP
  },
  run: function() {
    document.documentElement.style.setProperty('--registered', i);
    window.getComputedStyle(document.documentElement).getPropertyValue('--registered'); // Force style recalculation
    i = (i == 0) ? 1 : 0;
  },
  teardown: () => {
    document.documentElement.style.removeProperty('--registered');
  },
  done: (results) => {
    resetCSS();
    resolve(results);
  },
});

معیارهایی که انواع دیگر ویژگی‌ها را آزمایش می‌کنند به همین ترتیب کار می‌کنند، اما bootstrap خالی دارند زیرا هیچ خاصیتی برای ثبت وجود ندارد.

نتایج

اجرای این بنچمارک ها با 20 تکرار در مک بوک پرو 2021 (Apple M1 Pro) با 16 گیگابایت رم میانگین های زیر را به دست می دهد:

  • دارایی معمولی که به ارث می رسد ( accent-color ): 163 اجرا در ثانیه (= 6.13 میلی ثانیه در هر اجرا)
  • دارایی سفارشی ثبت نشده ( --unregistered ): 256 اجرا در ثانیه (= 3.90 میلی ثانیه در هر اجرا)
  • دارایی سفارشی ثبت شده با inherits: true ( --registered ): 252 اجرا در ثانیه (= 3.96 میلی‌ثانیه در هر اجرا)

در اجراهای متعدد، معیارها نتایج مشابهی به همراه دارند.

نمودار میله ای با نتایج برای خواصی که به ارث می برند. اعداد بالاتر سریعتر عمل می کنند.
شکل: نمودار میله ای با نتایج برای خواصی که به ارث می برند. اعداد بالاتر سریعتر عمل می کنند.

نتایج نشان می دهد که ثبت یک ملک سفارشی در مقایسه با عدم ثبت ملک سفارشی هزینه بسیار کمی دارد. ویژگی‌های سفارشی ثبت‌شده که به ارث می‌رسند با ۹۸ درصد سرعت ویژگی‌های سفارشی ثبت‌نشده اجرا می‌شوند. در اعداد مطلق، ثبت ویژگی سفارشی 0.06 میلی‌ثانیه سربار اضافه می‌کند.

ارزیابی عملکرد ویژگی‌های CSS که ارث نمی‌برند

ویژگی های بعدی که باید معیار قرار گیرند، آنهایی هستند که ارث نمی برند. در اینجا فقط دو نوع ویژگی وجود دارد که می توان آنها را محک زد:

  • یک ویژگی معمولی که ارث نمی برد: z-index .
  • یک دارایی سفارشی ثبت شده با inherits: false : --registered-no-inherit .

ویژگی های سفارشی که ثبت نشده اند نمی توانند بخشی از این معیار باشند زیرا آن ویژگی ها همیشه ارث می برند.

معیارها

معیارها بسیار شبیه به سناریوهای قبلی هستند. برای آزمایش با --registered-no-inherit , ثبت دارایی زیر در مرحله bootstrap معیار تزریق می شود:

@property --registered-no-inherit {
  syntax: "<number>";
  initial-value: 0;
  inherits: false;
}

نتایج

اجرای این بنچمارک ها با 20 تکرار در مک بوک پرو 2021 (Apple M1 Pro) با 16 گیگابایت رم میانگین های زیر را به دست می دهد:

  • دارایی معمولی که ارث نمی برد: 290269 اجرا در ثانیه (= 3.44 µs در هر اجرا)
  • دارایی سفارشی ثبت شده که به ارث نمی رسد: 214110 اجرا در ثانیه (= 4.67 µs در هر اجرا)

آزمایش در چندین بار تکرار شد و این نتایج معمولی بود.

نمودار میله ای با نتایج برای خواصی که ارث نمی برند. اعداد بالاتر سریعتر عمل می کنند.
شکل: نمودار میله ای با نتایج برای خواصی که ارث نمی برند. اعداد بالاتر سریعتر عمل می کنند.

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

  • برای املاک معمولی، تعداد اجراها از 163 دوش در ثانیه به بیش از 290 هزار بار در ثانیه افزایش یافت، که 1780 درصد افزایش در عملکرد را نشان می‌دهد!
  • برای ویژگی های سفارشی، تعداد اجراها از 252 اجرا در ثانیه به بیش از 214 هزار بار در ثانیه افزایش یافته است که 848 درصد افزایش عملکرد را نشان می دهد!

نکته کلیدی در پشت این موضوع این است که استفاده inherits: false هنگام ثبت یک دارایی سفارشی تأثیر معناداری دارد. اگر می توانید دارایی سفارشی خود را با inherits: false ، قطعا باید.

معیار امتیاز: چندین ثبت ملک سفارشی

یکی دیگر از موارد جالب برای معیار، تأثیر ثبت تعداد زیادی از املاک سفارشی است. برای انجام این کار، مجدداً تست را با --registered-no-inherit انجام دهید و 25000 ثبت ملک سفارشی دیگر را از قبل انجام دهید. این ویژگی های سفارشی در :root استفاده می شود.

این ثبت‌ها در مرحله setup معیار انجام می‌شود:

setup: () => {
  const propertyRegistrations = [];
  const declarations = [];

  for (let i = 0; i < 25000; i++) {
    propertyRegistrations.push(`@property --custom-${i} { syntax: "<number>"; initial-value: 0; inherits: true; }`);
    declarations.push(`--custom-${i}: ${Math.random()}`);
  }

  setCSS(`${propertyRegistrations.join("\n")}
  :root {
    ${declarations.join("\n")}
  }`);
},

اجراها در ثانیه برای این معیار بسیار شبیه به نتیجه مربوط به "مالکیت سفارشی ثبت شده که به ارث نمی رسد" است (214110 اجرا در ثانیه در مقابل 213158 اجرا در ثانیه) ، اما این بخش جالبی نیست که باید به آن نگاه کرد. پس از همه، می توان انتظار داشت که تغییر یک دارایی سفارشی تحت تأثیر ثبت سایر املاک قرار نگیرد.

بخش جالب این آزمون اندازه گیری تأثیر خود ثبت نام ها است. با مراجعه به DevTools، می توانید ببینید که 25000 ثبت مالکیت سفارشی دارای هزینه اولیه محاسبه مجدد سبک کمی بیش از 30ms هستند. پس از انجام این کار، وجود این ثبت‌نام‌ها هیچ تاثیر دیگری بر روی چیزها نخواهد داشت.

اسکرین شات DevTools با هزینه «محاسبه مجدد سبک» برای انجام ۲۵ هزار ثبت سفارشی دارایی برجسته شده است. راهنمای ابزار نشان می دهد که 32.42 میلی ثانیه طول کشیده است
شکل: اسکرین شات DevTools با هزینه "محاسبه مجدد سبک" برای انجام 25 هزار ثبت سفارشی دارایی برجسته شده است. راهنمای ابزار نشان می دهد که 32.42ms طول کشیده است

نتیجه گیری و نکات اولیه

به طور خلاصه سه نکته از همه اینها وجود دارد:

  • ثبت یک ملک سفارشی با @property هزینه عملکرد کمی دارد. این هزینه اغلب ناچیز است زیرا با ثبت ویژگی های سفارشی، پتانسیل کامل آنها را باز می کنید که دستیابی به آن بدون انجام آن ممکن نیست.

  • استفاده از inherits: false هنگام ثبت یک ملک سفارشی تأثیر معناداری دارد. با آن از ارث بردن اموال جلوگیری می کنید. هنگامی که مقدار ویژگی تغییر می کند، بنابراین به جای کل زیردرخت، تنها بر سبک های عنصر مطابقت یافته تأثیر می گذارد.

  • داشتن تعداد کمی از ثبت نام‌های @property بر محاسبه مجدد سبک تأثیری ندارد. هنگام انجام ثبت‌نام‌ها فقط هزینه اولیه بسیار کمی وجود دارد، اما وقتی این کار انجام شد، خوب هستید.