Skip to content

Conversation

@gurgunday
Copy link
Member

@gurgunday gurgunday commented Oct 25, 2025

Huge win by avoiding complex _copyActual logic when we can copy the whole source into target. The native copy implementation is faster only for partial copies.

Before:

buffers/buffer-concat-fill.js
buffers/buffer-concat-fill.js n=800000 extraSize=1: 3,080,309.438799309
buffers/buffer-concat-fill.js n=800000 extraSize=256: 2,718,494.3906977526
buffers/buffer-concat-fill.js n=800000 extraSize=1024: 2,592,930.9112553936

buffers/buffer-concat.js
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4: 6,895,296.007515873
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4: 6,850,915.824861098
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4: 6,714,498.121438699
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4: 6,606,244.795672606
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4: 3,875,370.506613865
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4: 4,427,107.007979794
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16: 2,202,621.1880456866
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16: 2,308,511.109946339
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16: 2,111,185.355049204
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16: 2,169,882.104202485
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16: 993,752.7633158871
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16: 1,010,066.0454915363

After:

buffers/buffer-concat-fill.js
buffers/buffer-concat-fill.js n=800000 extraSize=1: 4,213,313.932988885
buffers/buffer-concat-fill.js n=800000 extraSize=256: 3,999,625.0351529545
buffers/buffer-concat-fill.js n=800000 extraSize=1024: 3,271,098.409944823

buffers/buffer-concat.js
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4: 12,018,365.504695036
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4: 11,561,090.427597769
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4: 11,106,682.475733392
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4: 10,827,423.883277772
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4: 6,297,132.164792359
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4: 6,326,826.005617953
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16: 4,151,263.382265607
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16: 4,295,939.1963506
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16: 3,758,688.301977228
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16: 3,904,910.547041359
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16: 1,265,188.4389452678
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16: 1,284,207.4931866652

@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 25, 2025
@gurgunday gurgunday added the performance Issues and PRs related to the performance of Node.js. label Oct 25, 2025
@codecov
Copy link

codecov bot commented Oct 25, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.69%. Comparing base (2bda7cb) to head (7e75cf4).
⚠️ Report is 32 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #60399      +/-   ##
==========================================
+ Coverage   88.56%   88.69%   +0.12%     
==========================================
  Files         704      704              
  Lines      207774   208663     +889     
  Branches    40025    40676     +651     
==========================================
+ Hits       184022   185065    +1043     
+ Misses      15807    15677     -130     
+ Partials     7945     7921      -24     
Files with missing lines Coverage Δ
lib/buffer.js 100.00% <100.00%> (ø)

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

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

lgtm

@aduh95
Copy link
Contributor

aduh95 commented Oct 25, 2025

Benchmark GHA: https://github.com/aduh95/node/actions/runs/18809427089
Benchmark CI: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/1746/

Results (improvements across the board)
336.392                                                                              confidence improvement accuracy (*)   (**)  (***)
336.392  buffers/buffer-concat-fill.js n=800000 extraSize=1                                 ***     18.19 %       ±1.07% ±1.42% ±1.85%
336.392  buffers/buffer-concat-fill.js n=800000 extraSize=1024                              ***     12.89 %       ±0.87% ±1.16% ±1.51%
336.392  buffers/buffer-concat-fill.js n=800000 extraSize=256                               ***     16.73 %       ±0.87% ±1.16% ±1.51%
336.392  buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16          ***    100.26 %       ±1.68% ±2.26% ±2.99%
336.392  buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4           ***     56.72 %       ±2.03% ±2.72% ±3.60%
336.392  buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16         ***     96.79 %       ±1.62% ±2.17% ±2.87%
336.392  buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4          ***     60.41 %       ±2.24% ±3.00% ±3.93%
336.392  buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16        ***     12.61 %       ±0.53% ±0.71% ±0.93%
336.392  buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4         ***     23.27 %       ±1.02% ±1.36% ±1.77%
336.392  buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16          ***     98.73 %       ±1.27% ±1.71% ±2.26%
336.392  buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4           ***     57.20 %       ±2.52% ±3.36% ±4.41%
336.392  buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16         ***     96.40 %       ±1.37% ±1.85% ±2.44%
336.392  buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4          ***     57.44 %       ±2.48% ±3.32% ±4.36%
336.392  buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16        ***     11.19 %       ±0.61% ±0.81% ±1.07%
336.392  buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4         ***     21.48 %       ±1.03% ±1.38% ±1.80%
                                                                            confidence improvement accuracy (*)    (**)   (***)
buffers/buffer-concat-fill.js n=800000 extraSize=1                                 ***     82.25 %      ±16.94% ±22.34% ±28.67%
buffers/buffer-concat-fill.js n=800000 extraSize=1024                              ***     49.50 %      ±11.42% ±15.05% ±19.31%
buffers/buffer-concat-fill.js n=800000 extraSize=256                               ***     71.99 %      ±15.96% ±21.05% ±27.01%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16          ***    230.61 %      ±19.95% ±26.30% ±33.75%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4           ***    182.24 %      ±18.07% ±23.82% ±30.57%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16         ***    218.33 %      ±19.78% ±26.07% ±33.45%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4          ***    171.75 %      ±18.22% ±24.02% ±30.83%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16        ***     52.84 %      ±20.58% ±27.13% ±34.82%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4         ***     87.04 %      ±17.05% ±22.47% ±28.84%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16          ***    234.11 %      ±20.43% ±26.94% ±34.57%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4           ***    174.42 %      ±18.13% ±23.89% ±30.66%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16         ***    220.85 %      ±20.02% ±26.39% ±33.87%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4          ***    166.60 %      ±17.70% ±23.33% ±29.94%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16        ***     52.35 %      ±20.66% ±27.24% ±34.96%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4         ***     87.00 %      ±17.03% ±22.46% ±28.82%

Be aware that when doing many comparisons the risk of a false-positive
result increases. In this case, there are 15 comparisons, you can thus
expect the following amount of false-positive results:
  0.75 false positives, when considering a   5% risk acceptance (*, **, ***),
  0.15 false positives, when considering a   1% risk acceptance (**, ***),
  0.01 false positives, when considering a 0.1% risk acceptance (***)

@gurgunday
Copy link
Member Author

@aduh95 the CI didn't pick up the benchmarks, I think the category is buffers instead of buffer

This reverts commit 34adb7c.
Copy link
Member

@ChALkeR ChALkeR left a comment

Choose a reason for hiding this comment

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

Some more context
Here are the bench results on 22, 24, 25, and this PR

This PR almost fixes the perf regression in Buffer.concat from 22 to 24
It looks good, but what exactly caused the regression in the first place?

chalker@macbook-air node % nvm use 22
Now using node v22.21.0 (npm v10.9.4)
chalker@macbook-air node % node benchmark/buffers/buffer-concat.js                
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4: 15,549,190.105855972
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4: 14,168,246.61802625
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4: 15,250,695.217239005
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4: 14,881,656.04854307
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4: 10,125,850.525561389
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4: 10,183,947.552670104
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16: 6,768,836.156496439
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16: 7,270,903.994401986
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16: 6,108,494.482353465
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16: 6,542,388.544277659
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16: 2,367,896.0628124923
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16: 2,437,196.804720441
chalker@macbook-air node % nvm use 24                             
Now using node v24.10.0 (npm v11.6.1)
chalker@macbook-air node % node benchmark/buffers/buffer-concat.js
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4: 8,266,971.511241144
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4: 8,450,827.030151948
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4: 7,980,955.445069204
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4: 7,957,282.680666895
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4: 6,043,342.063646001
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4: 6,059,500.523097745
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16: 2,634,699.341399924
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16: 2,642,643.5743133803
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16: 2,489,485.812264763
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16: 2,499,791.361163519
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16: 1,448,505.2980656528
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16: 1,455,386.8242531868
chalker@macbook-air node % nvm use 25                             
Now using node v25.0.0 (npm v11.6.2)
chalker@macbook-air node % node benchmark/buffers/buffer-concat.js
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4: 8,443,602.204506326
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4: 8,741,819.91031439
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4: 8,261,916.466778204
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4: 8,200,330.6332309665
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4: 6,053,314.568058172
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4: 5,747,503.203784013
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16: 2,849,496.7008776525
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16: 2,931,439.135078883
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16: 2,680,797.822186869
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16: 2,744,654.5057383967
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16: 1,494,425.0957491235
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16: 1,482,919.7763571613
chalker@macbook-air node % ./out/Release/node.1 benchmark/buffers/buffer-concat.js
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4: 12,080,759.879796438
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4: 14,726,388.307247685
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4: 13,503,990.007047394
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4: 13,297,236.875731088
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4: 8,745,435.374665165
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4: 8,914,795.383326115
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16: 5,195,659.914599197
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16: 5,432,054.883146755
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16: 4,640,642.64269006
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16: 4,887,798.298893448
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16: 2,009,977.447651042
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16: 2,030,030.9224206505

@ChALkeR
Copy link
Member

ChALkeR commented Oct 26, 2025

This is strange, the situation makes little sense

This PR claims .set being faster than _copy, but #54087 which introduced _copy claimed the opposite

_copy instead of .set is still used in other places, not just in Buffer.concat

Reverting #54087 changes in lib/buffer.js gives the same perf improvement in buffers/buffer-concat.js as this PR

What is happening here?

@gurgunday
Copy link
Member Author

gurgunday commented Oct 26, 2025

No, they claimed an improvement for copy, which works with TypedArrays.

However, .concat, which only works with UInt8 arrays, did get slower because of this. Because it's now using a more complex logic as well while not needing it.

source = new Uint8Array(source.buffer, source.byteOffset + sourceStart, nb);

Funnily enough, we weren't using primordials here before, but anyway, that change is already made

@ChALkeR
Copy link
Member

ChALkeR commented Oct 26, 2025

@gurgunday Ah, I missed this being a different benchmark, thanks! But it still doesn't hold

First is this branch, second - reverting #54087 changes in lib/buffer.js

I see a ~2x improvement in non-partial case from revert

chalker@macbook-air node % ./out/Release/node.1 benchmark/buffers/buffer-copy.js
buffers/buffer-copy.js n=6000000 partial="true" bytes=8: 45,410,725.82422833
buffers/buffer-copy.js n=6000000 partial="false" bytes=8: 51,199,380.91073919
buffers/buffer-copy.js n=6000000 partial="true" bytes=128: 52,721,200.82657353
buffers/buffer-copy.js n=6000000 partial="false" bytes=128: 51,513,089.690728284
buffers/buffer-copy.js n=6000000 partial="true" bytes=1024: 42,652,878.82975401
buffers/buffer-copy.js n=6000000 partial="false" bytes=1024: 35,971,914.85553271
chalker@macbook-air node % ./out/Release/node benchmark/buffers/buffer-copy.js  
buffers/buffer-copy.js n=6000000 partial="true" bytes=8: 30,556,627.159494136
buffers/buffer-copy.js n=6000000 partial="false" bytes=8: 104,728,111.1614892
buffers/buffer-copy.js n=6000000 partial="true" bytes=128: 33,079,395.312182166
buffers/buffer-copy.js n=6000000 partial="false" bytes=128: 96,758,521.7890999
buffers/buffer-copy.js n=6000000 partial="true" bytes=1024: 28,040,817.260461506
buffers/buffer-copy.js n=6000000 partial="false" bytes=1024: 55,677,198.893486194

@ChALkeR
Copy link
Member

ChALkeR commented Oct 26, 2025

How about this?

Instead of this PR, apply this diff to the base branch

diff --git a/lib/buffer.js b/lib/buffer.js
index c9f45d33388..f82b6825712 100644
--- a/lib/buffer.js
+++ b/lib/buffer.js
@@ -252,7 +252,11 @@ function _copyActual(source, target, targetStart, sourceStart, sourceEnd) {
   if (nb <= 0)
     return 0;
 
-  _copy(source, target, targetStart, sourceStart, nb);
+  if (sourceStart === 0 && sourceEnd === sourceLen) {
+    TypedArrayPrototypeSet(target, source, targetStart);
+  } else {
+    _copy(source, target, targetStart, sourceStart, nb);
+  }
 
   return nb;
 }

Might need an extra check to ensure that's an uint8arr

Results (node.1 is this PR, node is the diff in this comment):

chalker@macbook-air node % ./out/Release/node.1 benchmark/buffers/buffer-copy.js
buffers/buffer-copy.js n=6000000 partial="true" bytes=8: 46,845,904.301625155
buffers/buffer-copy.js n=6000000 partial="false" bytes=8: 50,741,656.54307192
buffers/buffer-copy.js n=6000000 partial="true" bytes=128: 49,825,179.22407614
buffers/buffer-copy.js n=6000000 partial="false" bytes=128: 51,545,708.9047841
buffers/buffer-copy.js n=6000000 partial="true" bytes=1024: 42,703,170.17161358
buffers/buffer-copy.js n=6000000 partial="false" bytes=1024: 35,837,566.30240946
chalker@macbook-air node % ./out/Release/node benchmark/buffers/buffer-copy.js  
buffers/buffer-copy.js n=6000000 partial="true" bytes=8: 47,471,671.813901365
buffers/buffer-copy.js n=6000000 partial="false" bytes=8: 101,261,478.3683036
buffers/buffer-copy.js n=6000000 partial="true" bytes=128: 51,872,937.24023005
buffers/buffer-copy.js n=6000000 partial="false" bytes=128: 97,101,062.34873779
buffers/buffer-copy.js n=6000000 partial="true" bytes=1024: 42,115,718.20021988
buffers/buffer-copy.js n=6000000 partial="false" bytes=1024: 55,631,533.807746686
chalker@macbook-air node % ./out/Release/node.1 benchmark/buffers/buffer-concat.js
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4: 13,946,525.326070635
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4: 12,546,789.133156924
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4: 13,251,734.014242765
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4: 12,997,571.826125372
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4: 8,592,787.817549294
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4: 8,691,184.52718848
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16: 5,103,637.037397647
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16: 5,344,501.845730393
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16: 4,606,995.683786366
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16: 4,892,011.386376643
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16: 1,956,437.6702594468
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16: 1,954,990.617572372
chalker@macbook-air node % ./out/Release/node benchmark/buffers/buffer-concat.js  
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4: 14,095,126.032274174
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4: 14,524,987.517588852
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4: 13,516,814.630167618
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4: 13,178,458.98513511
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4: 8,433,984.771154312
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4: 8,881,528.766000504
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16: 5,155,880.805703049
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16: 5,525,322.983445663
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16: 4,684,892.216968649
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16: 4,980,325.13218421
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16: 1,955,496.7607208379
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16: 2,009,711.9329158156

(the slight difference in buffer-concat.js appears random / can be ignored)

Co-authored-by: Nikita Skovoroda <chalkerx@gmail.com>
@gurgunday
Copy link
Member Author

We might need a CI run to verify

@ChALkeR
Copy link
Member

ChALkeR commented Oct 26, 2025

Also, unrelated to this PR but related to the perf regression from 22 to 24

Whatever happed to allocUnsafe - does that work at all in 24+?

Since 24.0.0, there is no observable perf difference between alloc and allocUnsafe, and allocUnsafe always appears to be zero-filled

In that case places relying on allocUnsafe and then fill (like Buffer.concat([Buffer.alloc(0)], 1e6) would be ~2x faster (or more) if they just used alloc instead of allocUnsafe in the first place

allocUnsafe should be either fixed to be faster than alloc or should be replaced with alloc everywhere

upd: #60423

@gurgunday
Copy link
Member Author

I can remove isUint8Copy, but the duplicate check slows down .concat

This is pretty fast, removing most of the regressions of #54087 for both .concat and .copy while keeping the gains

main:

buffers/buffer-copy.js
buffers/buffer-copy.js n=6000000 partial="true" bytes=8: 40,621,680.975935884
buffers/buffer-copy.js n=6000000 partial="false" bytes=8: 41,741,453.437408686
buffers/buffer-copy.js n=6000000 partial="true" bytes=128: 40,867,909.33854818
buffers/buffer-copy.js n=6000000 partial="false" bytes=128: 38,587,659.50570816
buffers/buffer-copy.js n=6000000 partial="true" bytes=1024: 28,267,535.736348692
buffers/buffer-copy.js n=6000000 partial="false" bytes=1024: 24,461,058.268385213

buffers/buffer-concat-fill.js
buffers/buffer-concat-fill.js n=800000 extraSize=1: 3,257,088.077109085
buffers/buffer-concat-fill.js n=800000 extraSize=256: 3,147,272.683253827
buffers/buffer-concat-fill.js n=800000 extraSize=1024: 2,670,117.793915302

buffers/buffer-concat.js
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4: 6,980,252.046780044
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4: 6,830,178.28899765
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4: 6,819,840.744442903
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4: 6,719,693.899127752
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4: 4,519,928.301733328
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4: 4,529,137.222061498
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16: 2,276,024.9224729007
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16: 2,332,623.341103885
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16: 2,183,238.7775802654
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16: 2,215,731.7223715517
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16: 995,647.6197721304
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16: 1,001,899.5916066903

pr:

buffers/buffer-copy.js
buffers/buffer-copy.js n=6000000 partial="true" bytes=8: 39,361,200.516615756
buffers/buffer-copy.js n=6000000 partial="false" bytes=8: 79,086,897.4799316
buffers/buffer-copy.js n=6000000 partial="true" bytes=128: 40,317,918.07302284
buffers/buffer-copy.js n=6000000 partial="false" bytes=128: 74,925,504.38070068
buffers/buffer-copy.js n=6000000 partial="true" bytes=1024: 27,618,121.127528023
buffers/buffer-copy.js n=6000000 partial="false" bytes=1024: 40,185,949.49713848

buffers/buffer-concat-fill.js
buffers/buffer-concat-fill.js n=800000 extraSize=1: 4,014,117.1482457365
buffers/buffer-concat-fill.js n=800000 extraSize=256: 3,947,359.4940471966
buffers/buffer-concat-fill.js n=800000 extraSize=1024: 3,286,207.070069123

buffers/buffer-concat.js
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4: 11,725,434.299093843
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4: 11,560,888.60631508
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4: 10,916,068.559351033
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4: 10,753,290.298486339
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4: 6,269,989.706871398
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4: 5,705,964.543164859
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16: 4,078,167.4175609415
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16: 4,331,797.635265173
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16: 3,761,914.7009729715
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16: 3,921,810.5301073547
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16: 1,255,559.3637891742
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16: 1,251,530.6806879821

@ChALkeR
Copy link
Member

ChALkeR commented Oct 26, 2025

No objection to isUint8Copy, approach lgtm

@gurgunday gurgunday requested a review from aduh95 October 27, 2025 17:47
@aduh95
Copy link
Contributor

aduh95 commented Oct 27, 2025

Benchmark CI: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/1748/

                                                                            confidence improvement accuracy (*)   (**)  (***)
buffers/buffer-concat-fill.js n=800000 extraSize=1                                 ***     16.64 %       ±0.85% ±1.13% ±1.47%
buffers/buffer-concat-fill.js n=800000 extraSize=1024                              ***     12.33 %       ±0.75% ±1.00% ±1.30%
buffers/buffer-concat-fill.js n=800000 extraSize=256                               ***     14.95 %       ±0.95% ±1.26% ±1.65%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16          ***     90.82 %       ±1.60% ±2.13% ±2.79%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4           ***     49.53 %       ±2.49% ±3.34% ±4.42%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16         ***     85.79 %       ±1.31% ±1.76% ±2.33%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4          ***     53.38 %       ±1.86% ±2.49% ±3.27%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16        ***     11.16 %       ±0.55% ±0.74% ±0.96%
buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4         ***     18.39 %       ±0.96% ±1.28% ±1.67%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16          ***     95.93 %       ±2.18% ±2.94% ±3.90%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4           ***     55.37 %       ±1.71% ±2.29% ±3.00%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16         ***     84.77 %       ±1.88% ±2.53% ±3.35%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4          ***     50.96 %       ±2.28% ±3.05% ±4.02%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16        ***     11.73 %       ±0.78% ±1.04% ±1.36%
buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4         ***     19.58 %       ±0.86% ±1.15% ±1.50%

Be aware that when doing many comparisons the risk of a false-positive
result increases. In this case, there are 15 comparisons, you can thus
expect the following amount of false-positive results:
  0.75 false positives, when considering a   5% risk acceptance (*, **, ***),
  0.15 false positives, when considering a   1% risk acceptance (**, ***),
  0.01 false positives, when considering a 0.1% risk acceptance (***)

@nodejs-github-bot
Copy link
Collaborator

@gurgunday gurgunday added the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Oct 28, 2025
@gurgunday
Copy link
Member Author

gurgunday commented Oct 28, 2025

@aduh95 seems less fast than the first implementation, but copy (non-partial) got a lot faster too thanks to input from @ChALkeR

@gurgunday gurgunday closed this Oct 28, 2025
@gurgunday gurgunday reopened this Oct 28, 2025
@ChALkeR
Copy link
Member

ChALkeR commented Oct 28, 2025

seems less fast than the first implementation

How significantly?

Ah, I see numbers from CI.
Initial version:

18:58:03                                                                             confidence improvement accuracy (*)   (**)  (***)
18:58:03 buffers/buffer-concat-fill.js n=800000 extraSize=1                                 ***     18.19 %       ±1.07% ±1.42% ±1.85%
18:58:03 buffers/buffer-concat-fill.js n=800000 extraSize=1024                              ***     12.89 %       ±0.87% ±1.16% ±1.51%
18:58:03 buffers/buffer-concat-fill.js n=800000 extraSize=256                               ***     16.73 %       ±0.87% ±1.16% ±1.51%
18:58:03 buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16          ***    100.26 %       ±1.68% ±2.26% ±2.99%
18:58:03 buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4           ***     56.72 %       ±2.03% ±2.72% ±3.60%
18:58:03 buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16         ***     96.79 %       ±1.62% ±2.17% ±2.87%
18:58:03 buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4          ***     60.41 %       ±2.24% ±3.00% ±3.93%
18:58:03 buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16        ***     12.61 %       ±0.53% ±0.71% ±0.93%
18:58:03 buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4         ***     23.27 %       ±1.02% ±1.36% ±1.77%
18:58:03 buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16          ***     98.73 %       ±1.27% ±1.71% ±2.26%
18:58:03 buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4           ***     57.20 %       ±2.52% ±3.36% ±4.41%
18:58:03 buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16         ***     96.40 %       ±1.37% ±1.85% ±2.44%
18:58:03 buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4          ***     57.44 %       ±2.48% ±3.32% ±4.36%
18:58:03 buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16        ***     11.19 %       ±0.61% ±0.81% ±1.07%
18:58:03 buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4         ***     21.48 %       ±1.03% ±1.38% ±1.80%

Simple version (current):

18:54:16                                                                             confidence improvement accuracy (*)   (**)  (***)
18:54:16 buffers/buffer-concat-fill.js n=800000 extraSize=1                                 ***     16.64 %       ±0.85% ±1.13% ±1.47%
18:54:16 buffers/buffer-concat-fill.js n=800000 extraSize=1024                              ***     12.33 %       ±0.75% ±1.00% ±1.30%
18:54:16 buffers/buffer-concat-fill.js n=800000 extraSize=256                               ***     14.95 %       ±0.95% ±1.26% ±1.65%
18:54:16 buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=16          ***     90.82 %       ±1.60% ±2.13% ±2.79%
18:54:16 buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=1 pieces=4           ***     49.53 %       ±2.49% ±3.34% ±4.42%
18:54:16 buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=16         ***     85.79 %       ±1.31% ±1.76% ±2.33%
18:54:16 buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=16 pieces=4          ***     53.38 %       ±1.86% ±2.49% ±3.27%
18:54:16 buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=16        ***     11.16 %       ±0.55% ±0.74% ±0.96%
18:54:16 buffers/buffer-concat.js n=800000 withTotalLength=0 pieceSize=256 pieces=4         ***     18.39 %       ±0.96% ±1.28% ±1.67%
18:54:16 buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=16          ***     95.93 %       ±2.18% ±2.94% ±3.90%
18:54:16 buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=1 pieces=4           ***     55.37 %       ±1.71% ±2.29% ±3.00%
18:54:16 buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=16         ***     84.77 %       ±1.88% ±2.53% ±3.35%
18:54:16 buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=16 pieces=4          ***     50.96 %       ±2.28% ±3.05% ±4.02%
18:54:16 buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=16        ***     11.73 %       ±0.78% ±1.04% ±1.36%
18:54:16 buffers/buffer-concat.js n=800000 withTotalLength=1 pieceSize=256 pieces=4         ***     19.58 %       ±0.86% ±1.15% ±1.50%

Most of the improvement is there, but the simplified version is indeed consistently slower on concat by a bit
I recommend to land this first, then reevaluate if the previous change is worth it (it might as well be!)

@gurgunday
Copy link
Member Author

That works, I agree

Let's first fix non-partial copy degradation too

We can then see if we can go further

@ChALkeR

This comment was marked as resolved.

@gurgunday gurgunday requested a review from mcollina October 28, 2025 22:04
@richardlau
Copy link
Member

The AIX and LinuxOne CI failures look like real issues with this PR and is most likely due to not handling endianness properly.

@ChALkeR
Copy link
Member

ChALkeR commented Oct 28, 2025

@richardlau ah, you are right
@gurgunday That's an issue with the new testcase, not with the lib/* code. The expectation should differ on BE/LE. Or should check bytes of x, not values of x.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author ready PRs that have at least one approval, no pending requests for changes, and a CI started. buffer Issues and PRs related to the buffer subsystem. 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.

6 participants