Skip to content

perf(buf): route findIterBuf through packed transport - #71

Merged
jan-kubica merged 3 commits into
mainfrom
perf/buf-packed-transport
May 20, 2026
Merged

perf(buf): route findIterBuf through packed transport#71
jan-kubica merged 3 commits into
mainfrom
perf/buf-packed-transport

Conversation

@jan-kubica

Copy link
Copy Markdown
Contributor

findIterBuf wasn't routed through the packed Uint32Array transport that findIter uses, costing ~6-11x on large inputs (44k matches in our self-contained bench, audit reported 226ms vs 21ms on 140k matches). The Rust _findIterPackedBuf already existed at src/lib.rs:694; the JS wrapper in src/core.ts was still calling the per-match findIterBuf native path that round-trips a Vec<Match> across FFI. Plumbed the JS side through _findIterPackedBuf plus a ByteMatch unpacker. Added a bench assertion to keep findIterBuf within 2x of findIter, plus three targeted tests (ASCII parity with findIter, 10k-match packed-transport stress, empty result).

Offsets stay in bytes on the buffer path (UTF-16 translation only happens for the string API in Rust); ByteMatch keeps its pattern/start/end-only shape and the existing "no text field" test still passes.

Test plan

  • cargo check + cargo clippy --all-targets -- -D warnings clean
  • bun test green (139 tests, +3 new findIterBuf cases)
  • bench/buf-vs-string.ts reports findIterBuf within 2x of findIter (~0.65x locally)

findIterBuf went via the per-match napi Match path,
costing ~6-11x vs findIter on large inputs. Route it
through _findIterPackedBuf and unpack a Uint32Array
on the JS side. Byte offsets are preserved (no
UTF-16 translation on the buffer path).
Self-contained benchmark that synthesises a haystack
producing 44k matches, runs findIter and findIterBuf
on the same input, and fails (exit 1) if the buffer
path drifts to more than 2x the string path. Wired
into bench:all and exposed as bench:buf.

Also add three targeted findIterBuf tests covering
ASCII byte/UTF-16 parity with findIter, large match
counts (10k via packed Uint32Array), and the empty
result case.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request optimizes findIterBuf by implementing a packed Uint32Array transport mechanism, aligning it with the performance characteristics of the string-based findIter. Key changes include the addition of a performance benchmark, new test cases for large match counts and ASCII consistency, and the implementation of the unpackBuf utility. Feedback was provided regarding the safety of array initialization in unpackBuf, suggesting the use of Math.floor to avoid potential RangeError exceptions if the underlying data transport is malformed.

Comment thread src/core.ts Outdated
function unpackBuf(packed: Uint32Array): ByteMatch[] {
const len = packed.length;
// eslint-disable-next-line unicorn/no-new-array
const matches = new Array<ByteMatch>(len / 3);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The new Array(len / 3) call will throw a RangeError: Invalid array length if len is not a multiple of 3 (since it will be a floating-point number). While the native side is expected to return triples, it's safer to use Math.floor() to ensure a descriptive error is thrown by the subsequent logic instead of a cryptic RangeError if the transport is ever malformed.

Suggested change
const matches = new Array<ByteMatch>(len / 3);
const matches = new Array<ByteMatch>(Math.floor(len / 3));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed — new Array(len / 3) with a non-integer would throw RangeError: Invalid array length before the per-triple guard could surface a descriptive message. Fixed in ffc645c by wrapping the division in Math.floor. Applied the same change to the sibling unpack helper on line 144 since it carried the same risk.

CC on behalf of @jan-kubica

`new Array(packed.length / 3)` would throw a cryptic
`RangeError` if the native side ever returned a length
that is not a multiple of 3. Wrap the division in
`Math.floor` so the descriptive per-triple guard fires
instead. Applied to both `unpack` and `unpackBuf` to
keep the unpack family consistent.

Addresses gemini-code-assist review on #71.
@jan-kubica

Copy link
Copy Markdown
Contributor Author

Rabbit round on the gemini-code-assist review.

Summary review: the only actionable point was the unpackBuf array-init safety note; addressed in ffc645c. Wrapped the len / 3 division in Math.floor for both unpackBuf (cited) and the sibling unpack helper (same pattern on line 144). Added a short comment in each spot explaining why the floor is defensive.

Quality checks re-run locally: cargo check, cargo clippy --all-targets -- -D warnings, bun test (145 pass), bun run lint, bunx tsc --noEmit, and the buf-vs-string bench (findIterBuf / findIter = 0.64x, well within the 2x threshold).

CC on behalf of @jan-kubica

@jan-kubica
jan-kubica marked this pull request as ready for review May 20, 2026 21:11
@jan-kubica
jan-kubica merged commit 159021d into main May 20, 2026
20 checks passed
@jan-kubica
jan-kubica deleted the perf/buf-packed-transport branch May 20, 2026 21:11
@github-actions github-actions Bot locked and limited conversation to collaborators May 20, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant