Skip to content

Bench modules named on the command line - #31

Closed
kawanet-bot wants to merge 3 commits into
kawanet:mainfrom
kawanet-bot:feature/bench-dynamic
Closed

Bench modules named on the command line#31
kawanet-bot wants to merge 3 commits into
kawanet:mainfrom
kawanet-bot:feature/bench-dynamic

Conversation

@kawanet-bot

Copy link
Copy Markdown
Contributor

Comparing this package against itself — a published build, a branch build, the
working tree — used to mean copying a .mjs over dist/, running, and copying
it back. That mutates the tree, leaves a foreign build behind if the run is
interrupted, and puts each version in its own process, so the runner's
counterbalanced shuffle never sees them together.

Naming the files on the command line instead keeps every build in one rotation:

DURATION=500 SETS=5 node builder/bench.cli.ts \
  /tmp/built/0.10.7.mjs /tmp/built/74edcfc.mjs dist/sha256-uint8array.mjs
# node v24.19.0 DURATION=500 SETS=5 FILES=3
# string cccooooooooooooooo
# binary cccooooooooooooooo
...
|module|string|U8A|
|---|---|---|
|/tmp/built/0.10.7.mjs|245ms 🥈|183ms 🥈|
|/tmp/built/74edcfc.mjs|262ms|200ms|
|dist/sha256-uint8array.mjs|198ms 🥇|148ms 🥇|

Paths outrank TARGET and replace the comparison rather than filtering it, so
every cell measures a build of this package and only the code differs. The
header says FILES=n in place of TARGET=, since naming a target would be a
claim the run is not making.

Loading

Adapter gains a setup() hook, awaited once before any cell is built. A
module load is a cost of the import, not of a digest, so it stays outside every
timed window. The default does nothing, so the existing adapters are unchanged.

The adapter expects the documented createHash() entry point and says so if the
module lacks it. It is deliberately not a general adapter for arbitrary modules.
Correctness comes for free: the measured closures already assert each digest
against crypto, so a bad build fails before the timer starts.

Browsers

node:url joins node:assert and node:crypto in the alias lists of both
browser configs, pointing at a shim that throws rather than returning a
plausible URL — the CLI-only path never runs in a browser, and a fake result
would hide a broken split instead of reporting one. Both bundles build without
warnings and carry no reference to node:url beyond that shim's own comment.

Verified on Node v24.19.0 and Chromium 151: npm test green,
browser/tests.html 61 passes / 0 failures, and the four failure modes behave —
a missing path, a module without createHash, a module returning a wrong
digest, and a normal three-way comparison.

Comparing this package against itself — a published build, a branch, the
working tree — meant swapping dist/ in place between runs, which mutates
the tree and puts each build in its own process, out of reach of the
counterbalanced ordering. Naming the files instead keeps every build in
one rotation, and setup() gives an adapter somewhere to load them.

Co-authored-by: Claude <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@kawanet

kawanet commented Aug 23, 2026

Copy link
Copy Markdown
Owner

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dd2edc7f75

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread test/utils/adapters.ts
hash(data: string | Uint8Array | ArrayBufferView): string {
const createHash = this.loaded
if (!createHash) throw new Error(`${this.path}: setup() not awaited`)
const hash = createHash()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Isolate each dynamic module's createHash call site

When two or more paths are benchmarked, every DynamicModule instance shares this prototype method, so the shuffled measurements send different modules' createHash functions—and differently shaped hash objects through the later update/digest accesses—through the same V8 feedback sites. Unlike the per-adapter closures described above, this makes those sites polymorphic or megamorphic, so results can include cross-module inline-cache/deoptimization effects and vary with the number or order of compared files rather than isolating each build's hashing cost. Give each loaded module its own benchmark/hash closure so these hot call sites remain monomorphic.

Useful? React with 👍 / 👎.

kawanet-bot and others added 2 commits August 24, 2026 01:01
The overloads name the three input shapes one at a time, so a caller
holding a `string | Uint8Array` cannot hand it over: it has to narrow the
value back down first, which the implementation then does again. Declare
the union as well, and the extra branch inside the benchmark adapter goes
away with it.

Co-authored-by: Claude <noreply@anthropic.com>
One class instantiated per path shares its methods, so every module's
createHash and the differently shaped hashes it returns meet at the same
call sites — the megamorphic case the benchmark closures already avoid by
building one per adapter. Follow that rule here too. The effect is below
the noise at these input sizes, but the rule is cheap to keep.

Co-authored-by: Claude <noreply@anthropic.com>
@kawanet-bot

Copy link
Copy Markdown
Contributor Author

Superseded by #32, which carries the same feature on a fresh branch.

This branch had grown a commit that widened update()'s published overloads so
the benchmark adapter could hand it a union, and then a commit reverting it.
Neither belongs in main: the type definition is the package's public contract,
and a benchmark's convenience is not a reason to widen it. #32 solves it inside
the benchmark instead — lib/ and types/ are untouched — and squashes the
history so the detour never lands.

@kawanet-bot
kawanet-bot deleted the feature/bench-dynamic branch August 23, 2026 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants