Skip to content

Conversation

@ChALkeR
Copy link
Member

@ChALkeR ChALkeR commented Oct 31, 2025

See also #60372.
This is a somewhat less performant version than that PR, but significantly faster than main and is non semver-major (so it can be backported) and does not need doc changes.

Buffer.of is usually used on small static sizes (checked with GitHub codesearch)

The assumption that createUnsafeBuffer(size) is faster than new Uint8Array(size) / new FastBuffer(size) is incorrect for small sizes, as the latter does not call our allocator at all when it can go to heap, and the former is always an alloc.

Not letting it go to heap is hurting performance, direct allocations are slow.

See #26301 for context

But even past the v8_typed_array_max_size_in_heap size (which is 64 bytes in the default config), this is still not worse than the old version way past expected Buffer.of sizes

Before:

% out/Release/node.0 benchmark/buffers/buffer-of.js
buffers/buffer-of.js n=500000 len=0: 24,633,954.827485334
buffers/buffer-of.js n=500000 len=1: 26,160,564.38801611
buffers/buffer-of.js n=500000 len=2: 26,280,949.92493504
buffers/buffer-of.js n=500000 len=8: 22,938,007.012818534
buffers/buffer-of.js n=500000 len=64: 9,478,358.537868412
buffers/buffer-of.js n=500000 len=256: 2,702,711.2345046536
buffers/buffer-of.js n=500000 len=1024: 974,881.6079890658
buffers/buffer-of.js n=500000 len=2048: 528,368.570893787

After:

% out/Release/node.2 benchmark/buffers/buffer-of.js
buffers/buffer-of.js n=500000 len=0: 33,703,264.18068379
buffers/buffer-of.js n=500000 len=1: 35,216,496.263314925
buffers/buffer-of.js n=500000 len=2: 34,097,195.761377595
buffers/buffer-of.js n=500000 len=8: 27,875,878.880465902
buffers/buffer-of.js n=500000 len=64: 10,335,418.86238334
buffers/buffer-of.js n=500000 len=256: 2,763,676.639751257
buffers/buffer-of.js n=500000 len=1024: 981,007.050158241
buffers/buffer-of.js n=500000 len=2048: 523,017.20255880937

For comparison, #60372 (even faster, but does that via pooling):

% ./out/Release/node.pooled benchmark/buffers/buffer-of.js
buffers/buffer-of.js n=500000 len=0: 33,882,981.248046815
buffers/buffer-of.js n=500000 len=1: 44,826,164.135482594
buffers/buffer-of.js n=500000 len=2: 44,895,948.02845056
buffers/buffer-of.js n=500000 len=8: 31,774,014.66683433
buffers/buffer-of.js n=500000 len=64: 10,511,875.791675644
buffers/buffer-of.js n=500000 len=256: 3,666,954.9211037415
buffers/buffer-of.js n=500000 len=1024: 1,082,339.2273017906
buffers/buffer-of.js n=500000 len=2048: 554,083.8215035282

Realistically, sizes 0-8 are most important.

Benchmark code (taken from #60372 with some adjustments):

'use strict';

const common = require('../common.js');

// Measure Buffer.of(...items) throughput for various lengths.
// We prebuild the items array to avoid measuring array construction,
// and vary the effective iterations with length to keep total work reasonable.

const bench = common.createBenchmark(main, {
  len: [0, 1, 2, 8, 64, 256, 1024, 2048],
  n: [5e5],
});

function main({ len, n }) {
  if (len < 10) n *= 10; // noisy otherwise
  // Inline small numbers calls as this is fast enough for small changes to be significant
  switch (len) {
    case 0:
      bench.start();
      for (let i = 0; i < n; i++) Buffer.of();
      bench.end(n);
      break;
    case 1:
      bench.start();
      for (let i = 0; i < n; i++) Buffer.of(0);
      bench.end(n);
      break;
    case 2:
      bench.start();
      for (let i = 0; i < n; i++) Buffer.of(0, 1);
      bench.end(n);
      break;
    case 8:
      bench.start();
      for (let i = 0; i < n; i++) Buffer.of(0, 1, 2, 3, 4, 5, 6, 7);
      bench.end(n);
      break;
    default: {
      const items = new Array(len);
      for (let i = 0; i < len; i++) items[i] = i & 0xFF;
      bench.start();
      for (let i = 0; i < n; i++) {
          Buffer.of(...items);
      }
      bench.end(n);
    }
  }
}

@nodejs-github-bot nodejs-github-bot added buffer Issues and PRs related to the buffer subsystem. needs-ci PRs that need a full CI run. labels Oct 31, 2025
@codecov
Copy link

codecov bot commented Oct 31, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.57%. Comparing base (bdf03bf) to head (1a8aac1).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #60503      +/-   ##
==========================================
+ Coverage   88.05%   88.57%   +0.52%     
==========================================
  Files         704      704              
  Lines      207857   207858       +1     
  Branches    39964    40044      +80     
==========================================
+ Hits       183030   184117    +1087     
+ Misses      16806    15789    -1017     
+ Partials     8021     7952      -69     
Files with missing lines Coverage Δ
lib/buffer.js 100.00% <100.00%> (+1.38%) ⬆️

... and 105 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.

@anonrig anonrig added the needs-benchmark-ci PR that need a benchmark CI run. label Oct 31, 2025
@gurgunday
Copy link
Member

I'm not a huge fan of this approach - avoiding createUnsafeBuffer - makes things less consistent in my opinion

But anyway, maybe it could be a very targeted change for Buffer.of as we're guessing there will be less than 64 arguments

@ChALkeR
Copy link
Member Author

ChALkeR commented Oct 31, 2025

@gurgunday there is no significant difference on 1024/2048 according to the bench, but significant improvement on <=64 (which is the case almost always).

less consistent
could be a very targeted change for Buffer.of

That's why I left a comment there explaining the difference 🙃

@addaleax addaleax added the request-ci Add this label to start a Jenkins CI on a PR. label Oct 31, 2025
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Oct 31, 2025
@nodejs-github-bot
Copy link
Collaborator

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. needs-benchmark-ci PR that need a benchmark CI run. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants