قياس أداء 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
. وبمجرد الانتهاء من ذلك، فإن وجود هذه التسجيلات لن يكون له أي تأثير آخر على الأشياء.
الاستنتاج والوجبات السريعة
وخلاصة القول هناك ثلاث نقاط من كل هذا:
-
تسجيل عقار مخصص مع
@property
يأتي بتكلفة أداء طفيفة. غالبًا ما تكون هذه التكلفة ضئيلة لأنه من خلال تسجيل الخصائص المخصصة، فإنك تطلق العنان لإمكاناتها الكاملة والتي لا يمكن تحقيقها دون القيام بذلك. -
استخدام
inherits: false
عند تسجيل عقار مخصص له تأثير مفيد. وبه تمنع العقار من الميراث. عندما تتغير قيمة الخاصية، فإنها تؤثر فقط على أنماط العنصر المطابق بدلاً من الشجرة الفرعية بأكملها. -
وجود عدد قليل مقابل الكثير
@property
التسجيلات لا تؤثر على إعادة حساب النمط. لا توجد سوى تكلفة أولية صغيرة جدًا عند إجراء التسجيلات، ولكن بمجرد الانتهاء من ذلك، ستكون في وضع جيد.