Skip to content

buffer: improve performance of multiple Buffer operations#61871

Open
thisalihassan wants to merge 1 commit intonodejs:mainfrom
thisalihassan:buffer-perf-improvements
Open

buffer: improve performance of multiple Buffer operations#61871
thisalihassan wants to merge 1 commit intonodejs:mainfrom
thisalihassan:buffer-perf-improvements

Conversation

@thisalihassan
Copy link
Contributor

Summary

Multiple performance improvements to Buffer operations, verified with benchmarks (15-30 runs, comparing old vs new binaries built from same tree).

Changes

Buffer.copyBytesFrom() (+100-210%)
Avoid intermediate TypedArrayPrototypeSlice allocation by calculating byte offsets directly into the source TypedArray's underlying ArrayBuffer.

Buffer.prototype.toString('hex') (+10-30%)
Use V8's Uint8Array.prototype.toHex() builtin (via fast path in toString) instead of the C++ hexSlice binding.

Buffer.prototype.fill("t", "ascii") (+26-37%)

ASCII indexOf (+14-46%)
Call indexOfString directly for ASCII encoding instead of first converting the search value to a Buffer via fromStringFast and then calling indexOfBuffer. ASCII and Latin-1 share the same byte values for characters 0-127.

swap16/32/64 (+3-38%)
Add V8 Fast API C++ functions (FastSwap16/32/64) alongside the existing slow path. Largest gains at len=256 (+35%).

Benchmark results

Key results (15-30 runs, *** = p < 0.001):

Benchmark Improvement
copyBytesFrom (offset, Uint8Array, len=256) +210% ***
copyBytesFrom (offset+length, Uint8Array, len=256) +206% ***
swap16 len=256 +38% ***
fill("t", "ascii") size=8192 +37% ***
toString('hex') len=1024 +30% ***
indexOf ASCII 'Alice' +46% ***
indexOf ASCII '@' +31% ***
fill("t", "ascii") size=65536 +26% ***
toString('hex') len=64 +22% ***
swap64 len=768 aligned +12% ***

No regressions observed. Full benchmark CSV attached.
compare-all-buffers-final.csv

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/performance

@nodejs-github-bot nodejs-github-bot added buffer Issues and PRs related to the buffer subsystem. c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. labels Feb 17, 2026
@thisalihassan thisalihassan force-pushed the buffer-perf-improvements branch from d2ba38f to 495feb5 Compare February 17, 2026 21:41

function hexSliceToHex(buf, start, end) {
ensureUint8ArrayToHex();
return Uint8ArrayPrototypeToHex(
Copy link
Member

Choose a reason for hiding this comment

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

This is a flagged feature and will crash with --no-js-base-64.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Handled fallback

Comment on lines 1210 to 1217
void FastSwap16(Local<Value> receiver,
Local<Value> buffer_obj,
// NOLINTNEXTLINE(runtime/references)
FastApiCallbackOptions& options) {
HandleScope scope(options.isolate);
ArrayBufferViewContents<char> buffer(buffer_obj);
CHECK(nbytes::SwapBytes16(const_cast<char*>(buffer.data()), buffer.length()));
}
Copy link
Member

Choose a reason for hiding this comment

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

These fast callbacks are non-identical to the conventional callbacks they shadow.

  • The existing callbacks validate their argument and throws to JS if invalid, whereas your fast callbacks hard-crash the process. It might be better to validate in the JS layer, then use the same unwrapping logic on both sides.
  • Your fast callback cannot have a different return convention to the conventional callback. You will need to remove the return value from the conventional callback.

byteOffset,
encodingsMap.ascii,
dir),
indexOfString(buf, val, byteOffset, encodingsMap.latin1, dir),
Copy link
Member

Choose a reason for hiding this comment

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

Same behaviour AFAICT, but encodingsMap.ascii seems more appropriate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes same behaviour IndexOfString only has branches for UCS2, UTF8, and Latin1, Adding an ASCII branch to IndexOfString would just be a duplicate of the Latin1 branch since ASCII is a subset of Latin1.

Copy link
Member

Choose a reason for hiding this comment

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

That's my point, these are still discrete encodings even though the behaviour here is the same, so this should pass encodingsMap.ascii to the binding and IndexOfString should add a condition to send this down the same path.

Comment on lines 1210 to 1217
void FastSwap16(Local<Value> receiver,
Local<Value> buffer_obj,
// NOLINTNEXTLINE(runtime/references)
FastApiCallbackOptions& options) {
HandleScope scope(options.isolate);
ArrayBufferViewContents<char> buffer(buffer_obj);
CHECK(nbytes::SwapBytes16(const_cast<char*>(buffer.data()), buffer.length()));
}
Copy link
Member

Choose a reason for hiding this comment

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

Fast callbacks should include debug tracking and call tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for sharing these I will update the code

@Renegade334 Renegade334 added performance Issues and PRs related to the performance of Node.js. needs-benchmark-ci PR that need a benchmark CI run. labels Feb 17, 2026
@codecov
Copy link

codecov bot commented Feb 17, 2026

Codecov Report

❌ Patch coverage is 82.35294% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.73%. Comparing base (4f13746) to head (495feb5).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
src/node_buffer.cc 37.50% 15 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main   #61871   +/-   ##
=======================================
  Coverage   89.72%   89.73%           
=======================================
  Files         675      675           
  Lines      204806   204861   +55     
  Branches    39355    39358    +3     
=======================================
+ Hits       183761   183825   +64     
+ Misses      13330    13321    -9     
  Partials     7715     7715           
Files with missing lines Coverage Δ
lib/buffer.js 99.79% <100.00%> (+<0.01%) ⬆️
src/node_buffer.cc 67.18% <37.50%> (-0.81%) ⬇️

... and 49 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- copyBytesFrom: calculate byte offsets directly instead of
  slicing into an intermediate typed array
- toString('hex'): use V8 Uint8Array.prototype.toHex() builtin
- fill: add single-char ASCII fast path
- indexOf: use indexOfString directly for ASCII encoding
- swap16/32/64: add V8 Fast API functions
@thisalihassan thisalihassan force-pushed the buffer-perf-improvements branch from 495feb5 to 7181059 Compare February 17, 2026 23:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

buffer Issues and PRs related to the buffer subsystem. c++ Issues and PRs that require attention from people who are familiar with C++. needs-benchmark-ci PR that need a benchmark CI run. needs-ci PRs that need a full CI run. performance Issues and PRs related to the performance of Node.js.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants