tests: Vendor blink perf tests (#38654)

Vendors the [blink perf
tests](https://chromium.googlesource.com/chromium/src/+/HEAD/third_party/blink/perf_tests/).
These perf tests are useful to evaluate the performance of servo. 
The license that governs the perf tests is included in the folder. 
Running benchmark cases automatically is left to future work.

The update.py script is taken from mozjs and slightly adapted, so we can
easily filter
(and patch if this should be necessary in the future.

Testing: This PR just adds the perf_tests, but does not use or modify
them in any way.

---------

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
This commit is contained in:
Jonathan Schwender 2025-08-17 11:54:04 +02:00 committed by GitHub
parent 7621332824
commit ee781b71b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
648 changed files with 359694 additions and 0 deletions

View file

@ -0,0 +1,268 @@
(() => {
// Creates width <section> elements surrounded (optionally) by ChildNodeParts.
// Each <section> gets width children of the same kind, down to a depth of depth.
// If useParts is false, no Parts are constructed. If useParts is true, and
// chainParts is true, descendant Parts use ancestor Parts as their PartRoot.
// If chainParts is false, all Parts have the DocumentPartRoot as their root.
const createContent = (node, root, useParts, chainParts, width, depth, extra, at = 0) => {
at++;
for (let i=0; i<width; i++) {
const s = document.createComment('start');
const c = document.createElement('section');
c.textContent = `${at}.${i}`;
let extras = [];
for(let e=0;e<extra;e++) {
extras.push(Object.assign(document.createElement('span'),{classList:'extra'}));
}
if (i==0) {
const nodePartNode = document.createElement('section');
nodePartNode.textContent = 'nodepart';
extras.push(nodePartNode);
if (useParts) {
new NodePart(root, nodePartNode);
}
}
const e = document.createComment('e');
node.append(s, c, ...extras, e);
let nextLevelRoot = root;
if (useParts) {
const newPart = new ChildNodePart(root, s, e);
if (chainParts) {
nextLevelRoot = newPart;
}
}
if (at < depth) {
createContent(c, nextLevelRoot, useParts, chainParts, width, depth, extra, at);
}
}
}
const createTemplateWrapper = () => {
const template = document.createElement('template').content;
const wrapper = template.appendChild(document.createElement('div'));
return {template,wrapper};
}
let commentContent,partsFlatContent,partsNestedContent;
let contentCreated = false;
function createAllContent(width, depth, extra) {
contentCreated = true;
commentContent = createTemplateWrapper();
createContent(commentContent.wrapper, commentContent.template.getPartRoot(), /*useParts*/false, 0, width, depth, extra);
partsFlatContent = createTemplateWrapper();
createContent(partsFlatContent.wrapper, partsFlatContent.template.getPartRoot(), /*useParts*/true, /*chainParts*/false, width, depth, extra);
partsNestedContent = createTemplateWrapper();
createContent(partsNestedContent.wrapper, partsNestedContent.template.getPartRoot(), /*useParts*/true, /*chainParts*/true, width, depth, extra);
}
const errorCheck = (container) => {
if (!contentCreated) {
throw new Error('content must be created with createAllContent()');
}
if (!container.isConnected || container.ownerDocument != document) {
throw new Error('container must be in the document');
}
if (container.childNodes.length !== 0) {
throw new Error('container must be empty');
}
if (document.getPartRoot().getParts().length !== 0) {
throw new Error('test needs to start with no attached parts');
}
return {container};
}
const recursiveGetParts = (root,level=1) => {
let parts = Array.from(root.getParts());
const thisParts = [...parts];
for(let part of thisParts) {
if (part.getParts) {
parts.push(...recursiveGetParts(part,level+1));
}
}
return parts;
}
const countNodes = (node) => {
let c = 1;
node.childNodes.forEach(child => {
c += countNodes(child);
});
return c;
}
const countNodesAndParts = (state) => {
const nodes = countNodes(state.container);
const root = state.container.ownerDocument.getPartRoot();
let parts,nodeParts,childNodeParts;
if (!root.getParts().length) {
partCount = state.parts.length;
nodeParts = state.parts.filter(p => p instanceof FakeNodePart).length;
childNodeParts = state.parts.filter(p => p instanceof FakeChildNodePart).length;
} else {
const parts = recursiveGetParts(root);
partCount = parts.length;
nodeParts = parts.filter(p => p instanceof NodePart).length;
childNodeParts = parts.filter(p => p instanceof ChildNodePart).length;
}
return {nodes, partCount, nodeParts, childNodeParts};
}
class FakeChildNodePart {
start = null;
end = null;
constructor(start, end) {
this.start = start;
this.end = end;
}
}
class FakeNodePart {
node = null;
constructor(node) {
this.node = node;
}
}
const testList = [
{
test: "Raw, no parts",
prepare: (container) => errorCheck(container),
clone: (state) => {
state.clone = document.importNode(commentContent.template, true);
},
append: (state) => {
state.container.appendChild(state.clone);
},
getParts: (state) => {
state.parts = [];
},
},
{
test: "Manual tree walk",
prepare: (container) => {
state = errorCheck(container);
state.walker = document.createTreeWalker(document, 129 /* NodeFilter.SHOW_{ELEMENT|COMMENT} */);
return state;
},
clone: (state) => {
state.clone = document.importNode(commentContent.template, true);
},
append: (state) => {
state.container.appendChild(state.clone);
},
getParts: (state) => {
const parts = [];
state.walker.currentNode = state.container;
while (state.walker.nextNode()) {
const node = state.walker.currentNode;
if (node.nodeType === Node.COMMENT_NODE && node.textContent === 'start') {
parts.push(new FakeChildNodePart(node,node.nextSibling.nextSibling));
} else if (node.nodeType === Node.ELEMENT_NODE && node.firstChild?.nodeType === Node.TEXT_NODE && node.firstChild.textContent === 'nodepart') {
parts.push(new FakeNodePart(node));
}
}
state.parts = parts;
},
},
{
test: "Parts, flat",
prepare: (container) => errorCheck(container),
clone: (state) => {
state.clone = partsFlatContent.template.getPartRoot().clone().rootContainer;
},
append: (state) => {
state.container.appendChild(state.clone);
},
getParts: (state) => {
state.parts = document.getPartRoot().getParts();
},
},
{
test: "Parts, nested",
prepare: (container) => errorCheck(container),
clone: (state) => {
state.clone = partsNestedContent.template.getPartRoot().clone().rootContainer;
},
append: (state) => {
state.container.appendChild(state.clone);
},
getParts: (state) => {
state.parts = recursiveGetParts(document.getPartRoot());
},
},
];
const runTest = (testCase, repeats, container) => {
let cloneTime = 0;
let appendTime = 0;
let getPartsTime = 0;
let state;
for(let r=0;r<repeats;++r) {
// Clear out the old parts and content:
container.replaceChildren();
document.getPartRoot().getParts();
if (typeof window.GCController !== "undefined") {
// PerfTestRunner is providing GC.
PerfTestRunner.gc();
} else if (self.gc) {
// This requires --js-flags="--expose-gc" on the command line.
self.gc();
} else {
PerfTestRunner.assert_true(false,'This test requires some form of GC access');
}
state = testCase.prepare(container);
// Run the test
const start = performance.now();
testCase.clone(state);
const cloneDone = performance.now();
testCase.append(state);
const appendDone = performance.now();
testCase.getParts(state);
const partsDone = performance.now();
cloneTime += cloneDone - start;
appendTime += appendDone - cloneDone;
getPartsTime += partsDone - appendDone;
}
return {cloneTime, appendTime, getPartsTime, state};
}
const findTestCase = (testType) => {
const indx = testList.findIndex(v => v.test == testType);
PerfTestRunner.assert_true(indx >= 0,`Unable to find test for ${testType}`);
return testList[indx];
}
const runPerfTest = (testType,metric) => {
const width = 4;
const depth = 4;
const extra = 8;
const repeats = 10;
const container = document.createElement('div');
container.style="display:none";
document.body.appendChild(container);
PerfTestRunner.measureValue({
description: `This benchmark tests the ${metric} time for ${testType}, for the DOM Parts API`,
unit: 'ms',
setup: () => {
createAllContent(width, depth, extra);
},
run: function() {
const results = runTest(findTestCase(testType), repeats, container);
return results[metric];
},
warmUpCount: 2,
iterationCount: 30,
});
}
const manualRunTest = (testType, repeats, container) => {
return runTest(findTestCase(testType), repeats, container);
}
// Exposed functions:
window.runPerfTest =runPerfTest;
window.createAllContent = createAllContent;
window.manualRunTest = manualRunTest;
window.countNodesAndParts = countNodesAndParts;
})();