أنترنت

قياس أداء CSS @property | مقالات | web.dev


تاريخ النشر: 2 أكتوبر 2024

عند البدء في استخدام ميزة CSS جديدة، من المهم فهم تأثيرها على أداء مواقع الويب الخاصة بك، سواء كان ذلك إيجابيًا أو سلبيًا. مع @property الآن في Baseline، يستكشف هذا المنشور تأثير الأداء، والأشياء التي يمكنك القيام بها للمساعدة في منع التأثير السلبي.

قياس أداء CSS باستخدام PerfTestRunner

لقياس أداء CSS قمنا ببناء مجموعة اختبار “CSS Selector Benchmark”. يتم تشغيله بواسطة Chromium PerfTestRunner ويقيس تأثير أداء 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 Selector شجرة 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() يدويا.

لتشغيل هذه المعايير بشكل منفصل – دون أي امتدادات أو عوامل أخرى تتدخل – يتم تشغيل محرك الدمى من واجهة سطر الأوامر (CLI) لتحميل وتنفيذ المعيار الذي تم تمريره.

لهؤلاء @property المعايير على وجه التحديد بدلاً من زيارة الصفحة ذات الصلة على عنوان URL الخاص بها http://localhost:3000/benchmarks/at-rule/at-property.html استدعاء الأوامر التالية على CLI:

npm run benchmark at-rule/at-property

يؤدي هذا إلى تحميل الصفحة من خلال محرك الدمى، والمكالمات تلقائيًا window.startTest()، وتقارير النتائج.

قياس أداء خصائص CSS

لقياس أداء خاصية CSS، يمكنك قياس مدى سرعة التعامل مع إبطال النمط ومهمة إعادة حساب النمط اللاحقة التي يحتاج المتصفح إلى تنفيذها.

إبطال النمط هو عملية تحديد العناصر التي تحتاج إلى إعادة حساب نمطها استجابةً لتغيير في DOM. إن أبسط نهج ممكن هو إبطال كل شيء استجابة لكل تغيير.

عند القيام بذلك، يجب التمييز بين خصائص CSS التي ترث وخصائص CSS التي لا ترث.

  • عندما ترث خاصية CSS تغييرات على عنصر مستهدف، فإن أنماط جميع العناصر المحتملة في الشجرة الفرعية الموجودة أسفل العنصر المستهدف تحتاج أيضًا إلى التغيير.
  • عندما لا ترث خاصية CSS تغييرات على عنصر مستهدف، يتم إبطال أنماط هذا العنصر الفردي فقط.

نظرًا لأنه لن يكون من العدل مقارنة العقارات التي ترث مع العقارات التي لا ترث، فهناك مجموعتان من المعايير التي يجب تشغيلها:

  • مجموعة من المعايير مع الخصائص التي ترث.
  • مجموعة من المعايير ذات الخصائص التي لا ترث.

من المهم أن تختار بعناية الخصائص التي تريد قياسها. في حين أن بعض الخصائص (مثل accent-color) يؤدي فقط إلى إبطال الأنماط، وهناك العديد من الخصائص (مثل writing-mode) التي تبطل أيضًا أشياء أخرى مثل التخطيط أو الطلاء. تريد الخصائص التي تبطل الأنماط فقط.

لتحديد ذلك، ابحث عن الأشياء في قائمة Blink لخصائص CSS. كل عقار لديه حقل invalidate الذي يسرد ما يتم إبطاله.

علاوة على ذلك، من المهم أيضًا اختيار عقار لم يتم وضع علامة عليه independent من تلك القائمة، حيث أن قياس مثل هذه الخاصية من شأنه أن يؤدي إلى تحريف النتائج. العقارات المستقلة ليس لها أي آثار جانبية على العقارات أو الأعلام الأخرى. عندما تتغير الخصائص المستقلة فقط، يستخدم Blink مسار تعليمات برمجية سريعًا يستنسخ نمط السليل ويحدث القيم الجديدة في تلك النسخة المستنسخة. هذا الأسلوب أسرع من إجراء إعادة الحساب الكامل.

قياس أداء خصائص CSS الموروثة

تركز المجموعة الأولى من المعايير على خصائص CSS الموروثة. هناك ثلاثة أنواع من الخصائص التي ترث للاختبار والمقارنة مع بعضها البعض:

  • ملكية عادية ترث: accent-color.
  • خاصية مخصصة غير مسجلة: --unregistered.
  • خاصية مخصصة مسجلة بها inherits: true: --registered.

تتم إضافة الخصائص المخصصة غير المسجلة إلى هذه القائمة لأنها ترث بشكل افتراضي.

كما ذكرنا سابقًا، تم اختيار الخاصية الموروثة بعناية بحيث لا تؤدي إلا إلى إبطال الأنماط وأخرى لم يتم وضع علامة عليها على أنها independent.

أما بالنسبة للخصائص المخصصة المسجلة، فقط تلك التي لها الامتداد inherits يتم اختبار الواصف الذي تم تعيينه على “صحيح” في هذا التشغيل. ال inherits يحدد الواصف ما إذا كانت الخاصية ترث للأطفال أم لا. لا يهم ما إذا كانت هذه الخاصية مسجلة من خلال CSS @property أو جافا سكريبت 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 تكرارًا على جهاز MacBook Pro (Apple M1 Pro) لعام 2021 المزود بذاكرة وصول عشوائي (RAM) سعة 16 جيجابايت إلى الحصول على المتوسطات التالية:

  • الملكية العادية التي ترث (accent-color): 163 دورة في الثانية (= 6.13 مللي ثانية لكل تشغيل)
  • خاصية مخصصة غير مسجلة (--unregistered): 256 دورة في الثانية (= 3.90 مللي ثانية لكل تشغيل)
  • الملكية المخصصة المسجلة مع inherits: true (--registered): 252 دورة في الثانية (= 3.96 مللي ثانية لكل تشغيل)

على تشغيلات متعددة، تسفر المعايير عن نتائج مماثلة.

مخطط شريطي يوضح نتائج الخصائص الموروثة. الأرقام الأعلى تؤدي أداءً أسرع.
الشكل: مخطط شريطي يتضمن نتائج الخصائص الموروثة. الأرقام الأعلى تؤدي أداءً أسرع.

أظهرت النتائج أن تسجيل خاصية مخصصة يأتي بتكلفة ضئيلة للغاية بالمقارنة مع عدم تسجيل الخاصية المخصصة. تعمل الخصائص المخصصة المسجلة التي ترث بنسبة 98% من سرعة الخصائص المخصصة غير المسجلة. بالأرقام المطلقة، يؤدي تسجيل الخاصية المخصصة إلى إضافة حمل قدره 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 تكرارًا على جهاز MacBook Pro (Apple M1 Pro) لعام 2021 المزود بذاكرة وصول عشوائي (RAM) سعة 16 جيجابايت إلى الحصول على المتوسطات التالية:

  • الملكية العادية التي لا ترث: 290,269 دورة في الثانية (= 3.44 ميكروثانية لكل شوط)
  • الملكية المخصصة المسجلة التي لا ترث: 214,110 دورة في الثانية (= 4.67 ميكروثانية لكل شوط)

تم تكرار الاختبار على مدار عدة جولات وكانت هذه النتائج النموذجية.

مخطط شريطي يوضح نتائج الخصائص غير الموروثة. الأرقام الأعلى تؤدي أداءً أسرع.
الشكل: مخطط شريطي يتضمن نتائج الخصائص غير الموروثة. الأرقام الأعلى تؤدي بشكل أسرع.

الشيء الذي يبرز هنا هو أن الخصائص التي لا ترث تؤدي أداءً أسرع بكثير من الخصائص التي ترث. وبينما كان هذا متوقعًا بالنسبة للخصائص العادية، فإن هذا ينطبق أيضًا على الخصائص المخصصة.

  • بالنسبة للعقارات العادية ارتفع عدد مرات التشغيل من 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")}
  }`);
},

عمليات التشغيل في الثانية لهذا المعيار مشابهة جدًا لنتيجة “الملكية المخصصة المسجلة التي لا ترث” (214,110 مرة في الثانية مقابل 213,158 مرة في الثانية)، ولكن هذا ليس الجزء المثير للاهتمام الذي يجب النظر إليه. بعد كل شيء، من المتوقع أن تغيير خاصية مخصصة واحدة لا يتأثر بالتسجيلات من الخصائص الأخرى.

الجزء المثير للاهتمام من هذا الاختبار هو قياس تأثير التسجيلات نفسها. بالانتقال إلى DevTools، يمكنك أن ترى أن 25000 تسجيل ملكية مخصصة لها تكلفة أولية لإعادة حساب النمط تزيد قليلاً 30ms. وبمجرد الانتهاء من ذلك، فإن وجود هذه التسجيلات لن يكون له أي تأثير آخر على الأشياء.

لقطة شاشة لـ DevTools مع تمييز تكلفة
الشكل: لقطة شاشة لـ DevTools مع تمييز تكلفة “إعادة حساب النمط” لإجراء 25 ألف تسجيل للملكية المخصصة. يشير تلميح الأداة إلى أنه استغرق الأمر 32.42ms

الاستنتاج والوجبات السريعة

وخلاصة القول هناك ثلاث نقاط من كل هذا:

  • تسجيل عقار مخصص مع @property يأتي بتكلفة أداء طفيفة. غالبًا ما تكون هذه التكلفة ضئيلة لأنه من خلال تسجيل الخصائص المخصصة، فإنك تطلق العنان لإمكاناتها الكاملة والتي لا يمكن تحقيقها دون القيام بذلك.

  • استخدام inherits: false عند تسجيل عقار مخصص له تأثير مفيد. وبه تمنع العقار من الميراث. عندما تتغير قيمة الخاصية، فإنها تؤثر فقط على أنماط العنصر المطابق بدلاً من الشجرة الفرعية بأكملها.

  • وجود عدد قليل مقابل الكثير @property التسجيلات لا تؤثر على إعادة حساب النمط. لا توجد سوى تكلفة أولية صغيرة جدًا عند إجراء التسجيلات، ولكن بمجرد الانتهاء من ذلك، ستكون في وضع جيد.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

زر الذهاب إلى الأعلى