Files
Fulfilled-Knowledge/Understand-Anything-main/understand-anything-plugin/packages/dashboard/scripts/benchmark-aggregations.mjs
2026-05-27 15:40:32 +08:00

100 lines
3.3 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.
// Per-layer aggregation perf benchmark.
//
// Mirrors the BEFORE shape (graph.nodes.filter(n => layer.nodeIds.includes(n.id))
// per layer) and the AFTER shape (single nodesById Map + iterate layer.nodeIds)
// from `useOverviewGraph` in `src/components/GraphView.tsx`. Issue #102 reported
// a 4.8 MB knowledge graph that froze the dashboard on overview render — the
// quadratic Array.includes pass was the dominant synchronous cost.
//
// We can't import the dashboard helper directly (Vite-bundled, no
// per-module dist), so the new shape is reproduced here in lockstep with
// `src/utils/layerStats.ts::computeLayerStats`.
//
// Usage:
// node understand-anything-plugin/packages/dashboard/scripts/benchmark-aggregations.mjs
import { performance } from "node:perf_hooks";
function makeGraph(layerCount, nodesPerLayer) {
const nodes = [];
const layers = [];
for (let li = 0; li < layerCount; li++) {
const ids = [];
for (let ni = 0; ni < nodesPerLayer; ni++) {
const id = `n-${li}-${ni}`;
const complexity = ["simple", "moderate", "complex"][(li + ni) % 3];
nodes.push({ id, complexity });
ids.push(id);
}
layers.push({ id: `L${li}`, nodeIds: ids });
}
return { nodes, layers };
}
// --- BEFORE: O(N × K × L) per overview render ----------------------------
function aggregateBefore(graph) {
const out = [];
for (const layer of graph.layers) {
const memberNodes = graph.nodes.filter((n) => layer.nodeIds.includes(n.id));
const c = { simple: 0, moderate: 0, complex: 0 };
for (const n of memberNodes) c[n.complexity]++;
const aggregate =
c.complex > memberNodes.length * 0.3
? "complex"
: c.moderate > memberNodes.length * 0.3
? "moderate"
: "simple";
out.push({ id: layer.id, aggregateComplexity: aggregate });
}
return out;
}
// --- AFTER: O(N + Σ K_i) per overview render ----------------------------
function aggregateAfter(graph, nodesById) {
const out = [];
for (const layer of graph.layers) {
const c = { simple: 0, moderate: 0, complex: 0 };
let resolved = 0;
for (const nid of layer.nodeIds) {
const node = nodesById.get(nid);
if (!node) continue;
resolved++;
c[node.complexity]++;
}
const aggregate =
c.complex > resolved * 0.3
? "complex"
: c.moderate > resolved * 0.3
? "moderate"
: "simple";
out.push({ id: layer.id, aggregateComplexity: aggregate });
}
return out;
}
function bench(label, layerCount, nodesPerLayer) {
const graph = makeGraph(layerCount, nodesPerLayer);
const nodesById = new Map(graph.nodes.map((n) => [n.id, n]));
const t0 = performance.now();
const before = aggregateBefore(graph);
const t1 = performance.now();
const after = aggregateAfter(graph, nodesById);
const t2 = performance.now();
const beforeMs = t1 - t0;
const afterMs = t2 - t1;
const speedup = afterMs > 0 ? beforeMs / afterMs : Infinity;
const parity = JSON.stringify(before) === JSON.stringify(after);
console.log(
`${label} (${layerCount} layers × ${nodesPerLayer} nodes = ${graph.nodes.length} total): ` +
`BEFORE ${beforeMs.toFixed(1)}ms | AFTER ${afterMs.toFixed(1)}ms | ` +
`${speedup.toFixed(1)}× faster | parity ${parity}`,
);
}
bench("small", 10, 50);
bench("medium", 30, 100);
bench("large", 50, 200);
bench("issue#102 shape", 100, 200);