Files
cjk-autospace/test/bench/bench-scan.mjs
T
admin 1fae8ebf25 test(bench): 掃描層效能基準(jsdom,確定性 el.matches() 計數+計時)
Tier 2 基準設施:test/bench/bench-scan.mjs。建寬(多兄弟段落)+深(巢狀塊測 orphan、巢狀行內
測祖先游走)壓力結構,度量 render 之 el.matches() 呼叫數(確定性)與壁鐘中位數,供逐層提速實測。
正確補 Element.prototype.matches(非 HTMLBodyElement.prototype)。jsdom 無版面故僅覆蓋掃描層;
版面層(S1)另備 headless Chrome 基準。
2026-07-11 20:04:58 +08:00

147 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 掃描層效能基準(jsdom)。度量 render 之 CPU 掃描成本——確定性計數(el.matches() 呼叫數)
// +壁鐘中位數。jsdom 無版面(getClientRects 回 0),故 lineEdgegapTrim 不執行、此基準僅覆
// 蓋 charifyjiyajinzespacingorphanlongWord 之掃描;版面層(S1)另見 bench-layout。
//
// 用法:node test/bench/bench-scan.mjs [scale] [iters]
// scale:段落規模乘數(預設 60);iters:計時重複次數取中位數(預設 9)。
//
// 建立兩種壓力結構:寬(多兄弟段落,測線性)+深(巢狀塊測 orphan S2、巢狀行內測祖先游走/
// insidePill)。計數用正確之 Element.prototype.matches 補丁(非 HTMLBodyElement.prototype)。
import { JSDOM } from "jsdom";
import { createJuzhen } from "../../dist/juzhen.mjs";
const scale = Number(process.argv[2] || 60);
const iters = Number(process.argv[3] || 9);
/** 一段混合行內:透明(strong/em/span)、pillcode)、isolatemark)、opaquebutton)、
* 標點(句內/句末/括號引號)、中西混排、長英文詞、末尾實義字。 */
function para(i)
{
return '<p>第' + i + '段開頭文字'
+ '<strong>粗體<em>巢狀斜體</em>內容</strong>與'
+ '<span><span>雙層透明<code>codeSample</code>片段</span></span>'
+ '及<mark>標記字</mark>又<button>按鈕文字</button>'
+ '含 Latin word 與 supercalifragilistic 長詞,'
+ '句內點號、頓號;冒號:「引號」(括號)測試。末尾實義字甲乙丙丁。</p>';
}
/** 深巢狀塊(測 orphan S2 之逐塊 querySelector 掃子樹):div 套 depth 層,內含葉段落。 */
function deepBlock(depth, inner)
{
let s = inner;
for (let d = 0; d < depth; d += 1) { s = '<div>' + s + '</div>'; }
return s;
}
/** 深巢狀透明行內(測 insidePill/祖先游走:每文本節點沿祖先鏈走 depth 層)。 */
function deepInline(depth, i)
{
let s = '這是深層巢狀行內的文字內容甲乙丙丁。'
+ '<code>codeX</code>中間再一段實義字戊己庚辛。';
for (let d = 0; d < depth; d += 1) { s = '<span>' + s + '</span>'; }
return '<p>段' + i + s + '</p>';
}
function buildHtml()
{
let html = '<section lang="zh-Hant">';
// 寬:scale 段普通段落。
for (let i = 0; i < scale; i += 1) { html += para(i); }
// 深塊:若干「深 8 層 div 包一葉段落」的容器(放大 S2 的 O(Σ塊子樹))。
for (let i = 0; i < Math.max(4, scale / 6); i += 1)
{
html += deepBlock(8, para(1000 + i));
}
// 深行內:若干「深 10 層 span」的段落(放大 insidePill/祖先游走)。
for (let i = 0; i < Math.max(4, scale / 6); i += 1)
{
html += deepInline(10, 2000 + i);
}
html += '</section>';
return html;
}
const HTML = buildHtml();
function freshDom()
{
const dom = new JSDOM("<!doctype html><html><head></head><body>" + HTML + "</body></html>");
globalThis.document = dom.window.document;
globalThis.NodeFilter = dom.window.NodeFilter;
globalThis.Node = dom.window.Node;
globalThis.window = dom.window;
return dom;
}
/** 找到 matches 所在原型(Element.prototype),返回 { proto, restore }。 */
function patchMatches(dom, counter)
{
let p = dom.window.document.querySelector("strong");
let proto = Object.getPrototypeOf(p);
while (proto && !Object.prototype.hasOwnProperty.call(proto, "matches"))
{
proto = Object.getPrototypeOf(proto);
}
const orig = proto.matches;
proto.matches = function (s) { counter.n += 1; return orig.call(this, s); };
return () => { proto.matches = orig; };
}
function median(arr)
{
const s = arr.slice().sort((a, b) => a - b);
return s[Math.floor(s.length / 2)];
}
// ---- 計數(單次,確定性)----
{
const dom = freshDom();
const counter = { n: 0 };
const restore = patchMatches(dom, counter);
const jz = createJuzhen();
jz.render(dom.window.document.body);
restore();
const doc = dom.window.document;
console.log("結構規模:");
console.log(" 文本節點掃描後 jz-char:", doc.querySelectorAll("jz-char").length);
console.log(" jz-hws:", doc.querySelectorAll("jz-hws").length,
"| jz-jinze:", doc.querySelectorAll("jz-jinze").length,
"| jz-orphan:", doc.querySelectorAll("jz-orphan").length);
console.log(" DOM 元素總數:", doc.querySelectorAll("*").length);
console.log("計數(確定性):");
console.log(" render 期間 el.matches() 呼叫數:", counter.n);
}
// ---- 計時(render 中位數)----
{
const times = [];
for (let k = 0; k < iters; k += 1)
{
const dom = freshDom();
const jz = createJuzhen();
const t0 = performance.now();
jz.render(dom.window.document.body);
times.push(performance.now() - t0);
}
console.log("計時(" + iters + " 次中位數):");
console.log(" render:", median(times).toFixed(2), "ms (min",
Math.min(...times).toFixed(2) + ")");
}
// ---- render→revert 往返計時(第三層增量對照之基線)----
{
const times = [];
for (let k = 0; k < iters; k += 1)
{
const dom = freshDom();
const jz = createJuzhen();
jz.render(dom.window.document.body);
const t0 = performance.now();
jz.revert(dom.window.document.body);
jz.render(dom.window.document.body);
times.push(performance.now() - t0);
}
console.log(" revert+render 全樹往返:", median(times).toFixed(2), "ms");
}