เผยแพร่: 2 ต.ค. 2024
เมื่อเริ่มใช้ฟีเจอร์ CSS ใหม่ คุณควรทำความเข้าใจผลกระทบที่ฟีเจอร์ดังกล่าวมีต่อประสิทธิภาพของเว็บไซต์ ไม่ว่าจะเป็นผลเชิงบวกหรือเชิงลบ ตอนนี้ฟีเจอร์ @property
อยู่ในเกณฑ์พื้นฐานแล้ว โพสต์นี้จะอธิบายผลกระทบด้านประสิทธิภาพ และสิ่งที่คุณทำได้เพื่อช่วยป้องกันผลกระทบเชิงลบ
การเปรียบเทียบประสิทธิภาพของ CSS กับ PerfTestRunner
เราได้สร้างชุดทดสอบ "การเปรียบเทียบตัวเลือก CSS" เพื่อเปรียบเทียบประสิทธิภาพของ CSS เครื่องมือนี้ทำงานด้วย PerfTestRunner
ของ Chromium และทำการเปรียบเทียบประสิทธิภาพของ CSS PerfTestRunner
นี้เป็นสิ่งที่ Blink ซึ่งเป็นเครื่องมือแสดงผลพื้นฐานของ Chromium ใช้สำหรับการทดสอบประสิทธิภาพภายใน
Runner มีเมธอด 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
โดยสร้างต้นไม้ที่มีองค์ประกอบ 1, 000 รายการ โดยแต่ละองค์ประกอบมีองค์ประกอบย่อยที่ซ้อนอยู่ภายใน
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 ที่ไม่ได้รับค่าการเปลี่ยนแปลงในองค์ประกอบที่กำหนดเป้าหมายไว้ เฉพาะสไตล์ขององค์ประกอบนั้นๆ เท่านั้นที่จะใช้งานไม่ได้
เนื่องจากจะไม่เป็นธรรมที่จะเปรียบเทียบพร็อพเพอร์ตี้ที่สืบทอดมากับพร็อพเพอร์ตี้ที่ไม่มี จึงมีการเปรียบเทียบ 2 ชุดที่จะเรียกใช้ ดังนี้
- ชุดการเปรียบเทียบที่มีพร็อพเพอร์ตี้ที่รับช่วงมา
- ชุดการเปรียบเทียบที่มีพร็อพเพอร์ตี้ที่ไม่รับค่ามา
คุณควรเลือกพร็อพเพอร์ตี้ที่จะใช้เป็นข้อมูลเปรียบเทียบอย่างรอบคอบ แม้ว่าพร็อพเพอร์ตี้บางรายการ (เช่น accent-color
) จะลบล้างเฉพาะสไตล์ แต่ก็มีพร็อพเพอร์ตี้หลายรายการ (เช่น writing-mode
) ที่ลบล้างสิ่งอื่นๆ ด้วย เช่น เลย์เอาต์หรือการวาด คุณต้องการให้พร็อพเพอร์ตี้ทำให้สไตล์ใช้งานไม่ได้เท่านั้น
หากต้องการดูข้อมูลนี้ ให้ค้นหาในรายการพร็อพเพอร์ตี้ CSS ของ Blink แต่ละพร็อพเพอร์ตี้จะมีช่อง invalidate
ที่แสดงรายการที่ไม่ถูกต้อง
นอกจากนี้ ควรเลือกพร็อพเพอร์ตี้ที่ไม่ได้ทำเครื่องหมายเป็น independent
จากรายการนั้นด้วย เนื่องจากการเปรียบเทียบพร็อพเพอร์ตี้ดังกล่าวอาจทำให้ผลลัพธ์บิดเบือน พร็อพเพอร์ตี้อิสระจะไม่มีผลข้างเคียงต่อพร็อพเพอร์ตี้หรือ Flag อื่นๆ เมื่อมีการเปลี่ยนแปลงเฉพาะพร็อพเพอร์ตี้อิสระ Blink จะใช้เส้นทางโค้ดที่รวดเร็วซึ่งโคลนสไตล์ของรายการที่สืบทอดและอัปเดตค่าใหม่ในสำเนาที่โคลน วิธีนี้เร็วกว่าการคํานวณใหม่ทั้งหมด
การเปรียบเทียบประสิทธิภาพของพร็อพเพอร์ตี้ CSS ที่รับช่วงมา
ชุดการเปรียบเทียบชุดแรกมุ่งเน้นที่พร็อพเพอร์ตี้ CSS ที่รับค่ามา พร็อพเพอร์ตี้ที่รับค่ามาเพื่อทดสอบและเปรียบเทียบกันมี 3 ประเภท ได้แก่
- พร็อพเพอร์ตี้ทั่วไปที่รับค่าจาก:
accent-color
- พร็อพเพอร์ตี้ที่กำหนดเองที่ยังไม่ได้ลงทะเบียน:
--unregistered
- พร็อพเพอร์ตี้ที่กำหนดเองที่ลงทะเบียนกับ
inherits: true
:--registered
ระบบจะเพิ่มพร็อพเพอร์ตี้ที่กำหนดเองที่ไม่ได้ลงทะเบียนลงในรายการนี้เนื่องจากระบบจะรับค่าเหล่านี้โดยค่าเริ่มต้น
ดังที่ได้กล่าวไปก่อนหน้านี้ พร็อพเพอร์ตี้ที่รับค่ามาได้รับการเลือกอย่างรอบคอบเพื่อให้เป็นพร็อพเพอร์ตี้ที่ลบล้างเฉพาะสไตล์และไม่ได้ทําเครื่องหมายเป็น independent
สำหรับพร็อพเพอร์ตี้แบบกำหนดเองที่ลงทะเบียนไว้ จะมีการทดสอบเฉพาะพร็อพเพอร์ตี้ที่ตั้งค่าข้อบ่งชี้ inherits
เป็น "จริง" ในการเรียกใช้นี้ ตัวบ่งชี้ inherits
จะกำหนดว่าพร็อพเพอร์ตี้จะรับช่วงไปยังพร็อพเพอร์ตี้ย่อยหรือไม่ ไม่ว่าจะจดทะเบียนพร็อพเพอร์ตี้นี้ผ่าน CSS @property
หรือ JavaScript CSS.registerProperty
เนื่องจากการจดทะเบียนนั้นไม่ได้เป็นส่วนหนึ่งของการเปรียบเทียบ
เกณฑ์
ดังที่ได้กล่าวไปแล้ว หน้าเว็บที่มีข้อมูลเปรียบเทียบจะเริ่มต้นด้วยการสร้างแผนผัง DOM เพื่อให้หน้าเว็บมีชุดโหนดขนาดใหญ่พอที่จะดูผลกระทบของการเปลี่ยนแปลง
การเปรียบเทียบแต่ละรายการจะเปลี่ยนค่าของพร็อพเพอร์ตี้ซึ่งหลังจากนั้นจะทำให้เกิดการเปลี่ยนแปลงรูปแบบ โดยพื้นฐานแล้วการเปรียบเทียบจะวัดระยะเวลาที่หน้าเว็บต้องคำนวณใหม่ครั้งถัดไปเพื่อประเมินรูปแบบที่ไม่ถูกต้องทั้งหมดอีกครั้ง
หลังจากการเปรียบเทียบประสิทธิภาพ 1 รายการเสร็จสิ้นแล้ว ระบบจะรีเซ็ตสไตล์ที่แทรกเพื่อให้การเปรียบเทียบประสิทธิภาพครั้งถัดไปเริ่มต้นได้
เช่น การเปรียบเทียบที่วัดประสิทธิภาพของการเปลี่ยนรูปแบบของ --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 ปี 2021 (Apple M1 Pro) ที่มี RAM 16 GB ให้ค่าเฉลี่ยดังต่อไปนี้
- พร็อพเพอร์ตี้ปกติที่รับค่า (
accent-color
): การเรียกใช้ 163 ครั้งต่อวินาที (= 6.13 มิลลิวินาทีต่อการเรียกใช้ 1 ครั้ง) - พร็อพเพอร์ตี้ที่กำหนดเองที่ไม่ได้ลงทะเบียน (
--unregistered
): 256 การเรียกใช้ต่อวินาที (= 3.90 มิลลิวินาทีต่อการเรียกใช้) - พร็อพเพอร์ตี้ที่กำหนดเองที่ลงทะเบียนกับ
inherits: true
(--registered
): การเรียกใช้ 252 ครั้งต่อวินาที (= 3.96 มิลลิวินาทีต่อการเรียกใช้ 1 ครั้ง)
เมื่อทำการทดสอบหลายครั้ง ข้อมูลเปรียบเทียบจะให้ผลลัพธ์ที่คล้ายกัน
ผลลัพธ์แสดงให้เห็นว่าการจดทะเบียนพร็อพเพอร์ตี้ที่กำหนดเองมีต้นทุนเพียงเล็กน้อยเมื่อเทียบกับการไม่จดทะเบียนพร็อพเพอร์ตี้ที่กำหนดเอง พร็อพเพอร์ตี้ที่กำหนดเองที่ลงทะเบียนแล้วซึ่งรับค่ามาจะทํางานด้วยความเร็ว 98% ของพร็อพเพอร์ตี้ที่กําหนดเองที่ไม่ได้ลงทะเบียน ในแง่ตัวเลขสัมบูรณ์ การลงทะเบียนพร็อพเพอร์ตี้ที่กำหนดเองจะเพิ่มเวลาในการดำเนินการ 0.06 มิลลิวินาที
การเปรียบเทียบประสิทธิภาพของพร็อพเพอร์ตี้ CSS ที่ไม่ได้รับค่ามา
พร็อพเพอร์ตี้ถัดไปที่จะเปรียบเทียบคือพร็อพเพอร์ตี้ที่ไม่ได้รับค่ามา ต่อไปนี้คือพร็อพเพอร์ตี้ 2 ประเภทที่ใช้เปรียบเทียบได้
- พร็อพเพอร์ตี้ปกติที่ไม่ได้รับค่า:
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 ปี 2021 (Apple M1 Pro) ที่มี RAM 16 GB ให้ค่าเฉลี่ยดังต่อไปนี้
- พร็อพเพอร์ตี้ปกติที่ไม่ได้รับช่วง: การเรียกใช้ 290,269 ครั้งต่อวินาที (= 3.44μs ต่อการเรียกใช้)
- พร็อพเพอร์ตี้ที่กำหนดเองที่จดทะเบียนที่ไม่ได้รับค่า: การเรียกใช้ 214,110 ครั้งต่อวินาที (= 4.67μs ต่อการเรียกใช้)
มีการทดสอบซ้ำหลายครั้ง และนี่คือผลลัพธ์โดยทั่วไป
สิ่งที่โดดเด่นคือพร็อพเพอร์ตี้ที่ไม่ได้รับค่าสืบทอดมีประสิทธิภาพเร็วกว่าพร็อพเพอร์ตี้ที่รับค่าสืบทอดมาก แม้ว่านี่จะเป็นเรื่องปกติสำหรับพร็อพเพอร์ตี้ทั่วไป แต่พร็อพเพอร์ตี้ที่กำหนดเองก็มีลักษณะเช่นนี้เช่นกัน
- สำหรับพร็อพเพอร์ตี้ทั่วไป จำนวนการเรียกใช้เพิ่มขึ้นจาก 163 ครั้งต่อวินาทีเป็นมากกว่า 290,000 ครั้งต่อวินาที ซึ่งประสิทธิภาพเพิ่มขึ้น 1,780%
- สําหรับพร็อพเพอร์ตี้ที่กําหนดเอง จํานวนการเรียกใช้เพิ่มขึ้นจาก 252 ครั้งต่อวินาทีเป็นมากกว่า 214,000 ครั้งต่อวินาที ซึ่งประสิทธิภาพเพิ่มขึ้น 848%
สิ่งที่สําคัญที่ได้จากเรื่องนี้คือการใช้ inherits: false
เมื่อลงทะเบียนพร็อพเพอร์ตี้ที่กำหนดเองจะส่งผลที่มีความหมาย หากคุณลงทะเบียนพร็อพเพอร์ตี้ที่กำหนดเองกับ inherits: false
ได้ ก็ควรเป็นเช่นนั้น
เกณฑ์โบนัส: การจดทะเบียนพร็อพเพอร์ตี้ที่กำหนดเองหลายรายการ
อีกสิ่งที่น่าสนใจในการเปรียบเทียบคือผลกระทบของการลงทะเบียนพร็อพเพอร์ตี้ที่กำหนดเองจำนวนมาก โดยให้ทดสอบ --registered-no-inherit
อีกครั้งโดยทำการจดทะเบียนพร็อพเพอร์ตี้ที่กำหนดเองอื่นๆ ล่วงหน้า 25,000 รายการ พร็อพเพอร์ตี้ที่กำหนดเองเหล่านี้ใช้ใน :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 ครั้งต่อวินาที) แต่นี่ไม่ใช่ส่วนที่น่าสนใจ ท้ายที่สุดแล้ว การเปลี่ยนแปลงพร็อพเพอร์ตี้ที่กำหนดเองรายการหนึ่งจะไม่ได้รับผลกระทบจากการลงทะเบียนจากพร็อพเพอร์ตี้อื่นๆ
สิ่งที่น่าสนใจของการทดสอบนี้คือการวัดผลกระทบของการลงทะเบียน เมื่อไปที่เครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ คุณจะเห็นว่าการลงทะเบียนพร็อพเพอร์ตี้ที่กำหนดเอง 25,000 รายการมีต้นทุนการคำนวณสไตล์ใหม่ครั้งแรกมากกว่า 30ms
เล็กน้อย เมื่อดำเนินการเสร็จแล้ว การจดทะเบียนเหล่านี้จะไม่มีผลใดๆ เพิ่มเติม
สรุปและสิ่งที่ได้จากการศึกษา
โดยสรุปแล้ว ประเด็นสำคัญ 3 ข้อที่ได้จากข้อมูลทั้งหมดนี้ ได้แก่
การจดทะเบียนพร็อพเพอร์ตี้ที่กำหนดเองกับ
@property
จะมีค่าใช้จ่ายด้านประสิทธิภาพเล็กน้อย ค่าใช้จ่ายนี้มักน้อยมากเนื่องจากการลงทะเบียนพร็อพเพอร์ตี้ที่กําหนดเองทําให้คุณปลดล็อกศักยภาพสูงสุด ซึ่งจะไม่สามารถทำได้หากไม่ทำเช่นนั้นการใช้
inherits: false
เมื่อลงทะเบียนพร็อพเพอร์ตี้ที่กำหนดเองจะมีผลสำคัญ ซึ่งจะป้องกันไม่ให้พร็อพเพอร์ตี้รับค่า ดังนั้นเมื่อค่าของพร็อพเพอร์ตี้เปลี่ยนแปลง การเปลี่ยนแปลงดังกล่าวจะส่งผลต่อรูปแบบขององค์ประกอบที่ตรงกันเท่านั้น ไม่ใช่ทั้งซับต้นไม้การมีการลงทะเบียน
@property
เพียงไม่กี่รายการเทียบกับจำนวนมากจะไม่ส่งผลต่อการคำนวณรูปแบบใหม่ มีค่าใช้จ่ายล่วงหน้าเพียงเล็กน้อยเมื่อทำการลงทะเบียน แต่หลังจากนั้นคุณก็ไม่ต้องเสียค่าใช้จ่ายใดๆ อีก