openclaw/scripts/bench-plugin-invocation.ts
Peter Steinberger 6c3c9dd594
perf(plugins): reduce callback scope overhead (#154625)
* perf(plugins): reduce callback scope overhead

* perf(plugins): clarify benchmark method interception

* perf(plugins): register the invocation benchmark command

* perf(plugins): keep the measured invocation optimization focused

* perf(plugins): stabilize CI question fixture readiness
2026-09-21 11:40:38 +00:00

103 lines
3.8 KiB
TypeScript

// Run with pnpm bench:plugins:invocation.
import { AsyncLocalStorage } from "node:async_hooks";
import { performance } from "node:perf_hooks";
import { mock } from "node:test";
import { PluginInstance } from "../src/plugins/plugin-instance.js";
import { createEmptyPluginRegistry } from "../src/plugins/registry-empty.js";
import { createPluginRecord } from "../src/plugins/status.test-helpers.js";
const iterations = 10_000;
const instances: PluginInstance[] = [];
function instance(id: string) {
const registry = createEmptyPluginRegistry();
const record = createPluginRecord({ id, origin: "bundled" });
registry.plugins.push(record);
const owner = new PluginInstance(id, { record, registry });
instances.push(owner);
return owner;
}
// Synthetic registered shapes isolate invocation overhead from network/storage work.
const channel = instance("channel-fixture");
const onEvent = (value: number) => value + 1;
const channelView = channel.wrap({
callback: (callback: typeof onEvent) => callback(1),
returned: () => ({ run: onEvent }),
wrapper: (callback: typeof onEvent) => (value: number) => callback(value),
});
const returned = channelView.returned();
const wrapper = channelView.wrapper(onEvent);
const diagnostic = instance("diagnostics-fixture");
const observe = diagnostic.wrap({ observe: (_event: unknown) => undefined });
const event = { type: "model.usage", inputTokens: 12, outputTokens: 4 };
const catalog = instance("catalog-fixture");
const rows = Array.from({ length: 20 }, (_, index) => ({ id: String(index), title: "session" }));
const provider = catalog.wrap({ list: () => rows });
const scenarios = [
{ name: "diagnostics.observe", run: () => observe.observe(event) },
{ name: "sessionCatalog.list", run: () => provider.list() },
{ name: "channel.callback", run: () => channelView.callback(onEvent) },
{ name: "channel.returnedMethod", run: () => returned.run(1) },
{ name: "channel.returnedWrapper", run: () => wrapper(1) },
{ name: "channel.createReturnedWrapper", run: () => channelView.wrapper(onEvent)(1) },
];
try {
const results = [];
for (const scenario of scenarios) {
for (let index = 0; index < iterations; index++) {
scenario.run();
}
const samples = [];
for (let sample = 0; sample < 7; sample++) {
const start = performance.now();
for (let index = 0; index < iterations; index++) {
scenario.run();
}
samples.push(((performance.now() - start) * 1_000) / iterations);
}
let frames = 0;
let wraps = 0;
let entries = 0;
const run = mock.method(AsyncLocalStorage.prototype, "run");
const ProxyConstructor = Proxy;
const enter = Reflect.get(PluginInstance.prototype, "enter");
Reflect.set(
PluginInstance.prototype,
"enter",
new Proxy(enter, {
apply(target, receiver, args) {
entries++;
return Reflect.apply(target, receiver, args);
},
}),
);
globalThis.Proxy = new Proxy(ProxyConstructor, {
construct(target, args) {
wraps++;
return Reflect.construct(target, args);
},
});
try {
for (let index = 0; index < iterations; index++) {
scenario.run();
}
frames = run.mock.callCount();
} finally {
run.mock.restore();
run.mock.resetCalls();
globalThis.Proxy = ProxyConstructor;
Reflect.set(PluginInstance.prototype, "enter", enter);
}
results.push({
hook: scenario.name,
usPerCall: samples.toSorted((a, b) => a - b)[3],
framesPerCall: frames / iterations,
wrapsPerCall: wraps / iterations,
entriesPerCall: entries / iterations,
});
}
console.log(JSON.stringify({ node: process.version, iterations, samples: 7, results }, null, 2));
} finally {
await Promise.all(instances.map((owner) => owner.dispose()));
}