Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save jogibear9988/eb36095c7f06ee22e7dd665ff151f9cb to your computer and use it in GitHub Desktop.

Select an option

Save jogibear9988/eb36095c7f06ee22e7dd665ff151f9cb to your computer and use it in GitHub Desktop.
getboxquads-optimization-stage-benchmark.html
<!DOCTYPE html>
<meta charset="utf-8">
<title>GeometryUtils optimization stage benchmark</title>
<style>
body { font: 14px sans-serif; margin: 20px; }
#results { white-space: pre-wrap; }
#fixture { contain: layout style paint; }
</style>
<div id="fixture"></div>
<pre id="results">ready</pre>
<script>
'use strict';
const params = new URL(location.href).searchParams;
const scenarioName = params.get('scenario') || 'geometry';
const fixture = document.querySelector('#fixture');
const results = document.querySelector('#results');
const svgNamespace = 'http://www.w3.org/2000/svg';
function createGeometryScenario() {
const canvas = document.createElement('div');
fixture.appendChild(canvas);
let host = canvas;
for (let depth = 0; depth < 6; ++depth) {
const shadowHost = document.createElement('div');
host.appendChild(shadowHost);
host = shadowHost.attachShadow({mode: 'open'});
}
const targets = [];
const fragment = document.createDocumentFragment();
for (let index = 0; index < 1200; ++index) {
const target = document.createElement('div');
target.style.cssText =
`position:absolute;left:${index % 60}px;top:${index % 40}px;` +
'width:10px;height:8px;transform:rotate(0.2deg)';
fragment.appendChild(target);
targets.push(target);
}
host.appendChild(fragment);
document.body.offsetTop;
if (typeof targets[0].getBoxQuads !== 'function') {
throw new Error('GeometryUtils is not enabled');
}
return () => {
let checksum = 0;
for (const target of targets) {
checksum += target.getBoxQuads({relativeTo: canvas})[0].p3.x;
}
return checksum;
};
}
function appendTree(parent, branchCount, depth) {
const fragment = document.createDocumentFragment();
for (let branch = 0; branch < branchCount; ++branch) {
let node = document.createElement('div');
fragment.appendChild(node);
for (let level = 0; level < depth; ++level) {
const child = document.createElement('div');
node.appendChild(child);
node = child;
}
}
parent.appendChild(fragment);
}
function createTraversalScenario() {
const style = document.createElement('style');
style.textContent =
'.changed-a{color:rgb(10,20,30)}.changed-b{color:rgb(30,20,10)}';
document.head.appendChild(style);
const tree = document.createElement('div');
const scope = document.createElement('div');
tree.appendChild(scope);
fixture.appendChild(tree);
appendTree(tree, 1500, 4);
appendTree(scope, 40, 6);
let sharedParent = scope.children[6];
while (sharedParent.firstElementChild) {
sharedParent = sharedParent.firstElementChild;
}
const nestedTargetA = document.createElement('div');
const nestedTargetB = document.createElement('div');
sharedParent.append(nestedTargetA, nestedTargetB);
const targets = [
scope.children[4].lastElementChild,
scope.children[5].lastElementChild,
nestedTargetA,
nestedTargetB,
];
let phase = false;
document.body.offsetTop;
return () => {
phase = !phase;
const className = phase ? 'changed-a' : 'changed-b';
for (const target of targets) target.className = className;
return getComputedStyle(targets[3]).color.length;
};
}
function createPresentationScenario() {
const svg = document.createElementNS(svgNamespace, 'svg');
fixture.appendChild(svg);
const fragment = document.createDocumentFragment();
const elements = [];
for (let index = 0; index < 2500; ++index) {
const rect = document.createElementNS(svgNamespace, 'rect');
rect.setAttribute('x', index % 100);
rect.setAttribute('y', Math.floor(index / 100));
rect.setAttribute('width', '1');
rect.setAttribute('height', '1');
rect.setAttribute('opacity', '0.4');
fragment.appendChild(rect);
elements.push(rect);
}
svg.appendChild(fragment);
let phase = false;
document.body.offsetTop;
return () => {
phase = !phase;
const opacity = phase ? '0.4' : '0.6';
for (const element of elements) element.setAttribute('opacity', opacity);
return Number(getComputedStyle(elements.at(-1)).opacity);
};
}
function createSvgLayoutScenario() {
const svg = document.createElementNS(svgNamespace, 'svg');
const group = document.createElementNS(svgNamespace, 'g');
svg.appendChild(group);
fixture.appendChild(svg);
const fragment = document.createDocumentFragment();
const elements = [];
for (let index = 0; index < 3000; ++index) {
const rect = document.createElementNS(svgNamespace, 'rect');
rect.setAttribute('x', index * 2);
rect.setAttribute('y', '0');
rect.setAttribute('width', '1');
rect.setAttribute('height', '10');
fragment.appendChild(rect);
elements.push(rect);
}
group.appendChild(fragment);
const targets = [];
for (let index = 500; index < 600; index += 10) {
targets.push(elements[index]);
}
let phase = false;
document.body.offsetTop;
return () => {
phase = !phase;
const offset = phase ? 0.25 : 0.5;
for (let index = 0; index < targets.length; ++index) {
targets[index].setAttribute('x', 1000 + index * 20 + offset);
}
return group.getBBox().width;
};
}
const factories = {
geometry: createGeometryScenario,
traversal: createTraversalScenario,
presentation: createPresentationScenario,
'svg-layout': createSvgLayoutScenario,
};
let operation;
try {
operation = factories[scenarioName]();
} catch (error) {
window.__optimizationBenchmarkError = error.stack || String(error);
results.textContent = window.__optimizationBenchmarkError;
throw error;
}
async function measure(durationMs) {
const samples = [];
let operations = 0;
let checksum = 0;
const start = performance.now();
while (performance.now() - start < durationMs) {
const batchStart = performance.now();
checksum += operation();
samples.push(performance.now() - batchStart);
++operations;
if (operations % 20 === 0) {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
const elapsedMs = performance.now() - start;
samples.sort((a, b) => a - b);
const percentile = value =>
samples[Math.min(samples.length - 1,
Math.floor(samples.length * value))];
return {
operations,
elapsedMs,
operationsPerSecond: operations * 1000 / elapsedMs,
operationMs: {
average: samples.reduce((sum, value) => sum + value, 0) / samples.length,
p50: percentile(0.5),
p95: percentile(0.95),
},
checksum,
};
}
window.__optimizationBenchmarkReady = true;
window.__runOptimizationBenchmark = async function() {
await measure(1000);
const measurement = await measure(5000);
const result = {
scenario: scenarioName,
browser: navigator.userAgent,
measurement,
};
window.__optimizationBenchmarkResult = result;
results.textContent = JSON.stringify(result, null, 2);
return result;
};
if (params.get('autorun') === '1') window.__runOptimizationBenchmark();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment