test(napi/parser): investigate retained raw result costs

This commit is contained in:
Cameron committed 2026-11-04 10:13:21 +00:00
1 parent ce91724211
commit 77c641a10f
9 files changed
+3536 -31

No files matched your search

+91 -1
View File
@@ -12,13 +12,15 @@ Object)` gives the result `Object.prototype` while installing the private brand.
The constructor's prototype getters are only descriptor templates; the public
result does not inherit from that prototype.
## Throughput results
## Historical throughput results
Five serial paired fresh-process repetitions on 2026-11-03, Apple M4 Pro,
Node 26.13.0 / V8 14.6.202.34-node.40, matching local release bindings built with
`allocator,tokens`, production local JS printer. These are warmed corpus
measurements of the eager raw entry point, not full-build or cold-start results.
The public sync/async routing is covered by NAPI and contract tests.
These runs discarded results during warmup, including the retained workload.
The follow-up below corrects that mismatch without changing the runtime POC.
| Workload | Main median (range), seconds | POC median (range), seconds | Median change | Median max RSS MiB, main → POC |
| ----------------------------------------- | ---------------------------- | --------------------------- | --------------- | ------------------------------ |
@@ -39,6 +41,80 @@ private-wrapper samples are under `confirm/`; final samples use the actual edite
`eager.js`. The interactive host's background apps/indexers were not disabled;
final timing ranges and all individual samples are retained in `results.json`.
## Retained-result follow-up
Neither proposed replacement reliably removed the retained-AST regression. The
runtime remains the shared-getter POC at `ce917242118dab18f6b7529bf0cc02e9e6812911`.
[All follow-up samples, manifests, diagnostic summaries and exact trial patches](retention-results.json)
are preserved, including unfavorable samples and screening outliers.
Two alternatives were confirmed through the actual public eager implementation,
with five serial, rotated fresh-process repetitions per workload and matching
retention during warmup. Each series compared main, the original POC, and one
replacement using the same native/generated/printer artifacts:
| Retained workload, separate series | Main median | Original POC median | Replacement median | Replacement vs main |
| ------------------------------------------- | ----------: | ------------------: | -----------------: | ------------------: |
| Read-only data properties | 2.659s | 2.709s | 2.809s | 5.7% more time |
| Literal getters with private-field stamping | 2.587s | 2.631s | 2.667s | 3.1% more time |
The data-property replacement used a fresh literal in public field order and
`Object.defineProperties` to make its fields read-only. It changes accessors
into configurable data properties. The literal-getter replacement stamped
private fields onto a literal returned by a base constructor, removing both
`Reflect.construct` and `Object.defineProperties`. Its getters were unique per
result and referenced the receiver's private fields. The 13 focused tests and
64 contract combinations plus async cases passed for both trials, with their
descriptor/receiver differences made explicit. Neither replacement beat the
original POC's retained median in its own confirmation series, so neither was
kept. The literal lifetime probe reclaimed discarded payloads during minor GC,
but retained private payloads after deleting accessors from live owners.
An isolated construction probe wrapped 32,267 preallocated tiny payloads. Five
rotated repetitions had medians of 13.3ms for main, 25.4ms for the original POC,
25.6ms for shared-getter stamping, 27.6ms for read-only data properties, and
27.6ms for constructing an instance then resetting its prototype. A later
single literal-getter sample took 18.2ms; it is not a paired median. These
construction differences alone do not explain the historical 149ms retained
gap, and removing reflection did not improve the complete retained workload.
Warmup lifetime materially affects the comparison. The worker now retains
warmup results in `retain` mode, releases them before the premeasurement forced
GC, and records the policy. A final control alternated policy order over five
repetitions and reversed baseline/POC order on even repetitions:
| Warmup policy, original POC | Main median (range) | POC median (range) | Median change |
| --------------------------- | -------------------: | -------------------: | --------------: |
| Discard results | 3.166s (2.750–4.860) | 3.819s (3.553–4.547) | 20.6% more time |
| Retain results | 2.842s (2.728–3.482) | 2.869s (2.768–2.917) | 1.0% more time |
The matching-policy pairs included three slower and two faster POC results.
This corrects the benchmark's lifetime mismatch; it does not fix the runtime or
establish retained-workload parity. The host had background contention, and
absolute rates visibly shifted during the literal confirmation. The cause of
that shift was not established. Do not pool absolute timings between series.
The exploratory screen includes 17.5s main and 9.4s POC retention outliers;
none were removed from the evidence.
Matched Node 26.13.0 V8 source supports two separate costs. The constructor
[fast path checks the initial map's constructor](https://github.com/nodejs/node/blob/v26.13.0/deps/v8/src/builtins/builtins-constructor-gen.cc#L337),
so a target/new-target mismatch can take the runtime allocation path.
[`DefineProperties`](https://github.com/nodejs/node/blob/v26.13.0/deps/v8/src/objects/js-objects.cc#L1147)
still enumerates and converts descriptors before applying them; caching the
JavaScript descriptor objects does not eliminate that work. These source paths
are not a measurement of the optimized machine code's exact cost.
V8's [allocation-site pretenuring decisions](https://github.com/nodejs/node/blob/v26.13.0/deps/v8/src/heap/pretenuring-handler.cc#L43)
use survival feedback, so warmup lifetimes can change allocation behavior. In
separate instrumented retained passes, discarded warmup produced 1781MB promoted
for main versus 3869MB for the original POC; matching warmup produced 1941MB
versus 1553MB. Exploratory CPU profiles attributed 35% of main's samples and 61%
of the POC's samples to GC. These runs
were contended and instrumented, so their elapsed times are not throughput
claims. Disabling allocation-site pretenuring still left a retained gap
(main 3.595s, POC 3.937s, inline data 3.786s in one traced control): adaptive
pretenuring is not the sole cause. The remaining regression is unresolved.
## Compatibility
The POC preserves ordinary reads, own key order (`program`, `module`, `comments`,
@@ -163,6 +239,10 @@ pnpm -C packages/codegen build
node napi/parser/bench/raw-result/prepare.mjs
node napi/parser/bench/raw-result/contract.mjs
node napi/parser/bench/raw-result/run.mjs "$CORPUS" baseline,poc raw,maps,retain 3 paired
OXC_BENCH_WARMUP=discard node napi/parser/bench/raw-result/run.mjs "$CORPUS" baseline,poc retain 3 legacy-warmup
node napi/parser/bench/raw-result/run.mjs "$CORPUS" baseline,prior,stamped,data,literal raw,maps,retain 3 retention-screen
node napi/parser/bench/raw-result/contract.mjs literal
node --expose-gc napi/parser/bench/raw-result/construction.mjs stamped /tmp/stamped-construction.json
node napi/parser/bench/raw-result/run.mjs "$CORPUS" baseline,poc verify 1 correctness
node napi/parser/bench/raw-result/diagnose.mjs "$CORPUS"
node napi/parser/bench/raw-result/summarize.mjs paired
@@ -172,6 +252,9 @@ node --expose-gc --allow-natives-syntax napi/parser/bench/raw-result/lifetime.mj
The runner launches serial fresh processes and rotates variant order. Sources
are preloaded and hashed; two evenly spaced warmup passes precede forced GC.
The default `OXC_BENCH_WARMUP=matching` retains warmup results only in `retain`
mode, then releases them before forced GC. `OXC_BENCH_WARMUP=discard` reproduces
the historical behavior. Warmup owners are not held through the measured pass.
There is no forced GC inside the measured pass. Natural GC is included.
`raw` consumes AST body/error counts; `maps` prints the complete AST with source
maps; `retain` holds **every result** until after the pass and reads them again
@@ -194,6 +277,13 @@ paired run saves a build manifest with native/generated-JS/printer hashes.
`prepare.mjs` extracts main's wrapper from the pinned base, redirects all imports
to this worktree, and asserts its patch boundaries. Set `OXC_BENCH_BASE` only
when intentionally adapting the baseline to another revision.
The `prior` variant extracts the original POC at the pinned `ce917` commit.
`retention-results.json` embeds exact patches against that commit for the two
public implementation trials. To reproduce those trials, apply one patch in an
isolated checkout and compare `baseline,prior,poc`; the generated `data` variant
uses a helper and is not identical to the inline public trial. The constructor
probe retains wrappers around tiny preallocated payloads, includes natural GC,
and does not estimate complete parsing throughput.
Required focused checks:
+28 -1
View File
@@ -6,7 +6,8 @@ checkout and external reproduction were preserved. Implementation:
[`eager.js`](../../src-js/raw-transfer/eager.js). Method, commands and caveats:
[README](README.md). [All samples and checks](results.json).
Five paired fresh-process repetitions, Apple M4 Pro, Node 26.13.0:
Historical five paired fresh-process repetitions, Apple M4 Pro, Node 26.13.0.
These runs discarded results during warmup, including the retained workload:
| Workload | Main median | POC median | Change |
| ---------------------------------- | ----------: | ---------: | --------------: |
@@ -21,6 +22,28 @@ was similar at 4312 → 4269 MiB. Warmups, preloading and forced GC precede timi
profiles and GC instrumentation are separate. Earlier heavily contended partial
runs were excluded, and all final individual samples/ranges are preserved.
The follow-up investigated private-field stamping and read-only data properties.
Neither removed the retained regression, so the runtime POC remains unchanged.
In separate five-repetition confirmations through the public implementation,
data properties took 2.809s versus main's 2.659s and the original POC's 2.709s;
literal getters with private-field stamping took 2.667s versus 2.587s and 2.631s.
A single literal-getter construction sample was faster, but neither trial
established a complete retained-workload improvement. Both passed the 13 focused tests
and 64 contract combinations plus async cases, with representation differences
explicitly checked.
The benchmark now matches warmup retention to the measured workload and releases
warmup owners before forced GC. Five counterbalanced repetitions of the original
POC measured 3.166s main versus 3.819s POC with discarded warmup (+20.6%), and
2.842s versus 2.869s with retained warmup (+1.0%; three slower, two faster pairs).
This corrects methodology, not runtime behavior. Matched V8 source and GC traces
support survival feedback affecting allocation-site pretenuring; a traced control
with that feature disabled still left a gap. The retained regression remains
unresolved. Background contention and visible rate changes limit comparisons;
absolute timings from different series must not be pooled. All completed samples,
including screening outliers, manifests, exact trial patches and diagnostic
summaries are in [the follow-up evidence](retention-results.json).
The evidence supports **old-space accessor pairs retaining getter closures and
their captured ASTs across minor GC**. In three fresh controlled repetitions,
all 128 wrappers died after two minor GCs, but all closure-backed payloads
@@ -49,6 +72,10 @@ imports match prior evidence. Generated output remains 217,840,905 bytes,
SHA-256 `c56c4b008d33671dc659dc7f48e677d98d96cdb789173e36acc8934e56818cc5`.
All 178,470 NAPI tests passed, as did focused Node 26.13 tests, parser conformance
with unchanged snapshots, generation, formatting and focused lint.
Those full-suite results remain applicable because the follow-up restored the
exact original runtime, native binding, generated deserializers and printer.
The follow-up reran focused tests and contract checks and exercised the updated
benchmark harness separately.
Baseline JSON/raw differences for metadata ranges and literal lone surrogates
were recorded separately. Local parent tests pass; the existing package file
@@ -0,0 +1,43 @@
// oxlint-disable no-console
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { performance } from "node:perf_hooks";
import { root } from "./prepare.mjs";
const [variant, destination] = process.argv.slice(2);
assert.ok(variant && destination);
const { wrapResult } = await import(
pathToFileURL(path.join(root, "target/raw-result", `${variant}.mjs`))
);
const inputs = Array.from({ length: 32267 }, () => ({
program: { body: [] },
module: {},
comments: [],
errors: [],
}));
for (let pass = 0; pass < 2; pass++) {
for (let i = 0; i < 257; i++) wrapResult(inputs[i], null);
}
globalThis.gc();
const retained = [];
const start = performance.now();
for (const data of inputs) retained.push(wrapResult(data, null));
const elapsedMilliseconds = performance.now() - start;
let checksum = 0;
for (let i = 0; i < retained.length; i++) {
assert.equal(retained[i].program, inputs[i].program);
checksum += retained[i].program.body.length + retained[i].errors.length;
}
const report = {
variant,
node: process.version,
v8: process.versions.v8,
count: retained.length,
elapsedMilliseconds,
checksum,
};
fs.writeFileSync(destination, JSON.stringify(report, null, 2) + "\n");
console.log(JSON.stringify(report));
process.exit(0);
+75 -26
View File
@@ -7,6 +7,15 @@ import { parseSync, parse } from "../../src-js/index.js";
import "./prepare.mjs";
const root = fileURLToPath(new URL("../../../../", import.meta.url));
const [variant = "poc"] = process.argv.slice(2);
assert.ok(["poc", "prior", "stamped", "data", "reset", "literal"].includes(variant));
const dataProperties = variant === "data";
const candidate =
variant === "poc"
? { parseSyncRaw: parseSync, parse }
: await import(pathToFileURL(path.join(root, "target/raw-result", `${variant}.mjs`)));
const actualSync = candidate.parseSyncRaw;
const actualAsync = candidate.parse;
const { parseSyncRaw: baselineSync, parse: baselineAsync } = await import(
pathToFileURL(path.join(root, "target/raw-result/baseline.mjs"))
);
@@ -20,7 +29,13 @@ const fixtures = [
["x.js", 'const x = "\ud800";'],
["x.js", "let x; let x;"],
];
const report = { node: process.version, optionsChecked: 0, jsonDifferences: [], compatibility: {} };
const report = {
variant,
node: process.version,
optionsChecked: 0,
jsonDifferences: [],
compatibility: {},
};
for (const range of [false, true]) {
for (const experimentalParent of [false, true]) {
for (const experimentalTokens of [false, true]) {
@@ -33,19 +48,31 @@ for (const range of [false, true]) {
};
for (const [filename, source] of fixtures) {
const baseline = baselineSync(filename, source, options);
const actual = parseSync(filename, source, options);
const actual = actualSync(filename, source, options);
assert.deepEqual(Reflect.ownKeys(actual), Reflect.ownKeys(baseline));
assert.equal(Object.getPrototypeOf(actual), Object.getPrototypeOf(baseline));
for (const key of Object.keys(baseline)) {
assert.deepEqual(actual[key], baseline[key], `${filename}: ${key}`);
const expectedDescriptor = Object.getOwnPropertyDescriptor(baseline, key);
const actualDescriptor = Object.getOwnPropertyDescriptor(actual, key);
assert.deepEqual(
{ ...actualDescriptor, get: null },
{ ...expectedDescriptor, get: null },
);
assert.equal(actualDescriptor.get.name, expectedDescriptor.get.name);
assert.equal(actualDescriptor.get.length, expectedDescriptor.get.length);
if (dataProperties) {
assert.deepEqual(actualDescriptor, {
value: actual[key],
writable: false,
enumerable: true,
configurable: true,
});
} else {
assert.deepEqual(
{ ...actualDescriptor, get: null },
{ ...expectedDescriptor, get: null },
);
assert.equal(actualDescriptor.get.name, expectedDescriptor.get.name);
assert.equal(actualDescriptor.get.length, expectedDescriptor.get.length);
}
assert.throws(() => {
actual[key] = null;
}, TypeError);
}
report.optionsChecked++;
if (!experimentalParent && !experimentalTokens) {
@@ -71,36 +98,58 @@ await Promise.all(
experimentalParent: true,
};
const expected = await baselineAsync(filename, source, options);
const actual = await parse(filename, source, options);
const actual = await actualAsync(filename, source, options);
for (const key of Object.keys(expected)) assert.deepEqual(actual[key], expected[key]);
}),
);
const options = { experimentalRawTransfer: true };
const baseline = baselineSync("x.js", "let x;", options);
const actual = parseSync("x.js", "let x;", options);
const foreign = parseSync("other.js", "let other;", options);
const actual = actualSync("x.js", "let x;", options);
const foreign = actualSync("other.js", "let other;", options);
for (const key of ["program", "module", "comments", "errors"]) {
const getBefore = Object.getOwnPropertyDescriptor(baseline, key).get;
const getAfter = Object.getOwnPropertyDescriptor(actual, key).get;
assert.equal(getBefore.call({}), baseline[key]);
assert.equal(new Proxy(baseline, {})[key], baseline[key]);
assert.throws(() => getAfter.call({}), TypeError);
assert.throws(() => new Proxy(actual, {})[key], TypeError);
assert.equal(getAfter.call(foreign), foreign[key]);
if (dataProperties) {
assert.equal(getAfter, undefined);
assert.equal(new Proxy(actual, {})[key], actual[key]);
assert.equal(Object.create(actual)[key], actual[key]);
} else {
assert.throws(() => getAfter.call({}), TypeError);
assert.throws(() => new Proxy(actual, {})[key], TypeError);
assert.equal(getAfter.call(foreign), foreign[key]);
}
}
report.compatibility = {
ownDescriptors: "equal",
ownKeyOrder: "equal",
prototype: "Object.prototype",
privateStorageVisibleToReflectOwnKeys: false,
getterIdentity: "shared instead of unique per result",
detachedForeignReceiver: "TypeError instead of original result field",
proxyWithDefaultReceiver: "TypeError instead of original result field",
getterOnAnotherResult: "returns receiver's field instead of original result field",
inheritedReceiver: "TypeError instead of original result field",
};
report.compatibility = dataProperties
? {
ownDescriptors: "read-only configurable data properties instead of accessors",
ownKeyOrder: "equal",
prototype: "Object.prototype",
proxyWithDefaultReceiver: "supported",
inheritedReceiver: "supported",
}
: {
ownDescriptors: "equal",
ownKeyOrder: "equal",
prototype: "Object.prototype",
privateStorageVisibleToReflectOwnKeys: false,
getterIdentity:
Object.getOwnPropertyDescriptor(actual, "program").get
=== Object.getOwnPropertyDescriptor(foreign, "program").get
? "shared instead of unique per result"
: "unique per result",
detachedForeignReceiver: "TypeError instead of original result field",
proxyWithDefaultReceiver: "TypeError instead of original result field",
getterOnAnotherResult: "returns receiver's field instead of original result field",
inheritedReceiver: "TypeError instead of original result field",
};
fs.writeFileSync(
path.join(root, "target/raw-result/contract.json"),
path.join(
root,
"target/raw-result",
variant === "poc" ? "contract.json" : `contract-${variant}.json`,
),
JSON.stringify(report, null, 2) + "\n",
);
console.log(JSON.stringify(report));
+21 -1
View File
@@ -8,6 +8,22 @@ const nextTurn = () => new Promise((resolve) => setImmediate(resolve));
const descriptor = Object.getOwnPropertyDescriptor;
// oxlint-disable-next-line typescript/no-implied-eval -- V8 intrinsic syntax requires a flagged runtime.
const inYoungGeneration = new Function("value", "return %InYoungGeneration(value)");
class ResultBase {
constructor(result) {
return result;
}
}
class LiteralResult extends ResultBase {
#data;
constructor(data) {
super({
get program() {
return this.#data;
},
});
this.#data = data;
}
}
let privateDescriptors;
class PrivateResult {
#data;
@@ -65,6 +81,7 @@ function wrap(data) {
});
}
if (variant === "private") return Reflect.construct(PrivateResult, [data], Object);
if (variant === "literal") return new LiteralResult(data);
if (variant === "plain") return { program: data };
throw new Error(variant);
}
@@ -112,7 +129,10 @@ globalThis.gc();
const major = alive();
console.log("LIFETIME_END");
assert.equal(initialYoung, refs.length);
assert.equal(major.payload, operation === "delete" && variant === "private" ? refs.length : 0);
assert.equal(
major.payload,
operation === "delete" && ["private", "literal"].includes(variant) ? refs.length : 0,
);
assert.equal(major.wrapper, operation === "delete" ? refs.length : 0);
let afterRelease;
if (operation === "delete") {
+106 -1
View File
@@ -44,6 +44,9 @@ const bodies = {
define: " return createResult(data, tokens);",
bound: " return createResult(data, tokens);",
private: " return Reflect.construct(RawResult, [data, tokens], Object);",
stamped: " return new RawResult(data, tokens);",
literal: " return new RawResult(data, tokens);",
data: " return createResult(data, tokens);",
plain: " if (tokens !== null) data.tokens = tokens;\n return data;",
};
const helpers = {
@@ -56,13 +59,107 @@ const helpers = {
.join(
",",
)}, get tokens() { return this.#tokens; }, get errors() { return this.#data.errors; } });\ntokenDescriptors=all;\ndescriptors={program:all.program,module:all.module,comments:all.comments,errors:all.errors};\n}\n}`,
stamped: `let descriptors, tokenDescriptors;
class ResultBase {
constructor(tokens) {
return Object.defineProperties({}, tokens === null ? descriptors : tokenDescriptors);
}
}
class RawResult extends ResultBase {
#data;
#tokens;
constructor(data, tokens) {
super(tokens);
this.#data = data;
this.#tokens = tokens;
}
get program() { return this.#data.program; }
get module() { return this.#data.module; }
get comments() { return this.#data.comments; }
get tokens() { return this.#tokens; }
get errors() { return this.#data.errors; }
static {
const all = Object.getOwnPropertyDescriptors(this.prototype);
for (const key of ["program", "module", "comments", "tokens", "errors"]) all[key].enumerable = true;
descriptors = { program: all.program, module: all.module, comments: all.comments, errors: all.errors };
tokenDescriptors = { program: all.program, module: all.module, comments: all.comments, tokens: all.tokens, errors: all.errors };
}
}`,
data: `const descriptors = { program: { writable: false }, module: { writable: false }, comments: { writable: false }, errors: { writable: false } };
const tokenDescriptors = { ...descriptors, tokens: { writable: false } };
function createResult(data, tokens) {
const result = tokens === null
? { program: data.program, module: data.module, comments: data.comments, errors: data.errors }
: { program: data.program, module: data.module, comments: data.comments, tokens, errors: data.errors };
return Object.defineProperties(result, tokens === null ? descriptors : tokenDescriptors);
}`,
literal: `class ResultBase {
constructor(result) { return result; }
}
class RawResult extends ResultBase {
#data;
#tokens;
constructor(data, tokens) {
super(tokens === null ? {
get program() { return this.#data.program; },
get module() { return this.#data.module; },
get comments() { return this.#data.comments; },
get errors() { return this.#data.errors; },
} : {
get program() { return this.#data.program; },
get module() { return this.#data.module; },
get comments() { return this.#data.comments; },
get tokens() { return this.#tokens; },
get errors() { return this.#data.errors; },
});
this.#data = data;
this.#tokens = tokens;
}
}`,
};
for (const [name, body] of Object.entries(bodies)) {
const source =
original.slice(0, start) + body + "\n}\n" + original.slice(end) + "\n" + (helpers[name] ?? "");
original.slice(0, start)
+ body
+ "\n}\n"
+ original.slice(end)
+ "\n"
+ (helpers[name] ?? "")
+ `\nexport function wrapResult(data, tokens) {\n${body}\n}\n`;
fs.writeFileSync(path.join(output, `${name}.mjs`), toLocal(source));
}
const prior = execFileSync(
"git",
["show", "ce917242118dab18f6b7529bf0cc02e9e6812911:napi/parser/src-js/raw-transfer/eager.js"],
{
cwd: root,
encoding: "utf8",
},
);
fs.writeFileSync(
path.join(output, "prior.mjs"),
toLocal(prior)
+ "\nexport function wrapResult(data, tokens) { return Reflect.construct(RawResult, [data, tokens], Object); }\n",
);
const reset = prior.replace(
"return Reflect.construct(RawResult, [data, tokens], Object);",
"return Object.setPrototypeOf(new RawResult(data, tokens), Object.prototype);",
);
assert.notEqual(reset, prior);
fs.writeFileSync(
path.join(output, "reset.mjs"),
toLocal(reset)
+ "\nexport function wrapResult(data, tokens) { return Object.setPrototypeOf(new RawResult(data, tokens), Object.prototype); }\n",
);
const tracked = [
"napi/parser/bench/raw-result/prepare.mjs",
"napi/parser/bench/raw-result/run.mjs",
"napi/parser/bench/raw-result/worker.mjs",
"napi/parser/bench/raw-result/corpus.mjs",
"napi/parser/bench/raw-result/construction.mjs",
"napi/parser/bench/raw-result/contract.mjs",
"napi/parser/bench/raw-result/lifetime.mjs",
"napi/parser/src-js/index.js",
"napi/parser/src-js/raw-transfer/eager.js",
"napi/parser/src-js/raw-transfer/common.js",
"napi/parser/src-js/generated/constants.js",
@@ -99,6 +196,14 @@ fs.writeFileSync(
arch: process.arch,
cpu: os.cpus()[0].model,
baselineSourceSha256: createHash("sha256").update(original).digest("hex"),
variants: Object.fromEntries(
[...Object.keys(bodies), "prior", "reset"].map((name) => [
name,
createHash("sha256")
.update(fs.readFileSync(path.join(output, `${name}.mjs`)))
.digest("hex"),
]),
),
hashes,
},
null,
File diff suppressed because it is too large. Load diff
+1
View File
@@ -53,6 +53,7 @@ for (let run = 0; run < Number(runsText); run++) {
const previous = results.find((row) => row.mode === mode);
if (previous) {
assert.deepEqual(result.corpus, previous.corpus);
assert.deepEqual(result.warmup, previous.warmup);
assert.equal(result.checksum, previous.checksum);
}
results.push(result);
+5 -1
View File
@@ -23,6 +23,8 @@ assert.ok(
"worker.mjs CORPUS VARIANT MODE OUTPUT [LIMIT] [none|cpu|heap]",
);
assert.ok(["raw", "maps", "retain", "verify"].includes(mode));
const warmupRetention = process.env.OXC_BENCH_WARMUP ?? "matching";
assert.ok(["matching", "discard"].includes(warmupRetention));
const root = fileURLToPath(new URL("../../../../", import.meta.url));
const { parseSyncRaw } = await import(
variant === "poc"
@@ -111,10 +113,11 @@ function execute(file, measured = false) {
if (mode !== "verify") {
for (let pass = 0; pass < 2; pass++) {
for (let i = 0; i < files.length; i += Math.max(1, Math.floor(files.length / 256))) {
execute(files[i]);
execute(files[i], mode === "retain" && warmupRetention === "matching");
}
}
}
retained.length = 0;
checksum = errors = 0;
globalThis.gc();
let session;
@@ -166,6 +169,7 @@ const report = {
v8: process.versions.v8,
flags: process.execArgv,
profile,
warmup: { passes: mode === "verify" ? 0 : 2, retention: warmupRetention },
corpus: metadata,
elapsedMilliseconds,
cpuMilliseconds: (cpuUsage.user + cpuUsage.system) / 1000,