Repository navigation
feat(core): allocation-free *Into variants for hot paths - #201
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
eca3ad1 to
ee33734
Compare
Add out-first, allocation-free twins of the hot per-pixel / per-frame functions, in the style of pmndrs/math. Each writes into `out`, returns it, allocates nothing (intermediate stages use module-level scratch), and is bit-identical to its allocating counterpart: - Color <-> space: toOklabInto, fromOklabInto, toLinearSrgbInto, fromLinearSrgbInto, toRgbInto, fromRgbInto, toP3Into, fromP3Into - Low-level: srgbToLinearInto, linearToSrgbInto, linearRgbToOklabInto, oklabToLinearRgbInto, oklabToOklchInto, oklchToOklabInto, linearSrgbToLinearP3Into, linearP3ToLinearSrgbInto, linearP3ToP3Into, p3ToLinearP3Into - mixInto, interpolateInto (including the interpolation options) - toSrgbGamutInto, toP3GamutInto The allocating functions are now thin wrappers (`fn(x) = fnInto(new, x)`), so there is no duplicated math. inSrgbGamut/inP3Gamut and the gamut-mapping bisection no longer allocate per call, and fixupHues shares its rules with an allocation-free hue lerp. A differential check against the pre-refactor source (c681f71) over 100k random inputs per function found 0 mismatches (Object.is on every field) for every refactored allocating API, parse, contrast, mix/interpolate and generateScale. To keep the everyday import from growing, parse() now shares one number/percentage helper and one alpha helper across its branches (same results). Size-limit: parse+toHex+contrastRatio 2337 -> 2300 B, everyday toolkit 2386 -> 2351 B. Adds `pnpm --filter @color-kit/core bench:into` (ns/op, B/op, GC count over 1e6 iterations) and into.test.ts (identity, bit-identity and aliasing), plus reference-accuracy coverage of the Into variants against colorjs.io. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…sors The `*Into` pipelines share module-level scratch. If an input was an object with accessors whose getter ran another conversion, the nested call could overwrite scratch that the outer call had already written (for example the OKLab stage, before the outer call read `alpha`), and corrupt the result. Snapshot inputs before any shared scratch is written: - Every low-level kernel (srgb, oklab, oklch, p3) reads all of its input fields, alpha included, into locals before writing `out`. Only the first stage of a pipeline reads caller input, so a nested call can only run before this call's scratch is written. - Gamut mapping works only from its l/c/h/alpha snapshot; the membership check now uses the trial color instead of re-reading the input. - interpolateInSpaceInto reads the options and both endpoints into locals, copies them to START/END scratch, and mixes from those. Results are unchanged: a differential check against the previous source over 100k random inputs per function found 0 mismatches across 30 functions (Object.is on every field). The functions still allocate nothing, and bench:into is unchanged within noise (all ops within about ±3%). New into.test.ts cases wrap Colors, Rgb, P3 and linear inputs with an `alpha` getter (and a variant with getters on every field) that runs toRgb, toP3, fromRgb, fromP3, gamut mapping and mix. All 6 cases fail on the previous source and pass now. Size-limit: parse+toHex+contrastRatio 2317 B, everyday toolkit 2367 B, both still below the pre-*Into baselines (2337 / 2386 B). Co-Authored-By: Claude Opus 5.5 <[email protected]>
Say that the *Into functions create no result or intermediate objects rather than that they allocate nothing: engines may still box doubles written into object fields (V8 does in code it does not fully inline), and V8 often scalar-replaces the allocating functions' temporaries. Recommend benchmarking at the call site. Co-Authored-By: Claude Opus 5.5 <[email protected]>
x / (width - 1) is NaN for a 1px-wide image. Use t = 0 (the start color) when width is 1. Co-Authored-By: Claude Opus 5.5 <[email protected]>
ee33734 to
93d87b8
Compare
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|

Stack 2/4. Adds
out-first, allocation-free*Intovariants (conversions, low-level kernels,mixInto/interpolateInto,toSrgbGamutInto/toP3GamutInto). Allocating APIs become thin wrappers over the kernels — bit-identical (0 mismatches, 100k inputs × 30 functions).bench:into: Into variants ~0 B/op, 0 GCs per 1e6 ops (vs up to 28); ns/op roughly equal.toRgb/toP3allocate 1 object instead of 4.Recommendation: adopt in addition — object API stays primary; Into reduces GC count, not time. Typed arrays (#202) are the better zero-alloc per-pixel target.
🤖 Generated with Claude Code