Skip to content

Commit bb67759

Browse files
committed
bench: add buffers() benchmarks
Covers the new Source.prototype.buffers() method across the cases where it matters: - concat-source: flat (10 raw) and nested (4x10 raw) — isolates the Buffer.concat avoided by buffers(). - cached-source: cold and warm paths against a CachedSource wrapping a ConcatSource, so both "first call" and "hit the cache" are observable. - compat-source: delegated vs super-fallback to mirror the buffer() benchmarks. - realistic-source-map-pipeline: reproduces the CachedSource -> ConcatSource -> CachedSource -> ConcatSource layering from issue #157 and measures cold + warm buffer() vs buffers(). Local wall-clock sample (not CodSpeed) shows ~3x on the flat ConcatSource and ~2.4x on the nested ConcatSource; warm paths are unchanged (CachedSource returns the cached array/buffer either way), which is the expected shape.
1 parent 9c63469 commit bb67759

5 files changed

Lines changed: 174 additions & 1 deletion

File tree

benchmark/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ stable than per-call timing for sub-microsecond work.
112112

113113
| Case | What it measures |
114114
| --------------------------------- | ----------------------------------------------------------------------------- |
115-
| `realistic-source-map-pipeline` | OriginalSource -> ReplaceSource -> ConcatSource -> CachedSource (cold + warm) |
115+
| `realistic-source-map-pipeline` | OriginalSource -> ReplaceSource -> ConcatSource -> CachedSource (cold + warm); also `buffer()` vs `buffers()` over the `CachedSource -> ConcatSource -> CachedSource -> ConcatSource` layering from issue #157 |
116116

117117
## Adding a new case
118118

benchmark/cases/cached-source/index.bench.mjs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ const warmed = (() => {
3636
return cached;
3737
})();
3838

39+
/**
40+
* A CachedSource wrapping a ConcatSource of 10 RawSources with buffers()
41+
* already populated. Used to measure warm buffers()/buffer() delegation.
42+
*/
43+
const warmedConcat = (() => {
44+
const parts = [];
45+
for (let i = 0; i < 10; i++) parts.push(new sources.RawSource(fixtureCode));
46+
const cached = new sources.CachedSource(new sources.ConcatSource(...parts));
47+
cached.buffers();
48+
return cached;
49+
})();
50+
3951
/**
4052
* @param {import("tinybench").Bench} bench bench
4153
*/
@@ -60,6 +72,38 @@ export default function register(bench) {
6072
for (let i = 0; i < 500; i++) warmed.buffer();
6173
});
6274

75+
bench.add("cached-source: buffers() (cached)", () => {
76+
for (let i = 0; i < 500; i++) warmed.buffers();
77+
});
78+
79+
bench.add("cached-source: buffer() (cold, wraps ConcatSource x10)", () => {
80+
for (let i = 0; i < 10; i++) {
81+
const parts = [];
82+
for (let j = 0; j < 10; j++) {
83+
parts.push(new sources.RawSource(fixtureCode));
84+
}
85+
new sources.CachedSource(new sources.ConcatSource(...parts)).buffer();
86+
}
87+
});
88+
89+
bench.add("cached-source: buffers() (cold, wraps ConcatSource x10)", () => {
90+
for (let i = 0; i < 10; i++) {
91+
const parts = [];
92+
for (let j = 0; j < 10; j++) {
93+
parts.push(new sources.RawSource(fixtureCode));
94+
}
95+
new sources.CachedSource(new sources.ConcatSource(...parts)).buffers();
96+
}
97+
});
98+
99+
bench.add("cached-source: buffer() (warm, wraps ConcatSource x10)", () => {
100+
for (let i = 0; i < 500; i++) warmedConcat.buffer();
101+
});
102+
103+
bench.add("cached-source: buffers() (warm, wraps ConcatSource x10)", () => {
104+
for (let i = 0; i < 500; i++) warmedConcat.buffers();
105+
});
106+
63107
bench.add("cached-source: size() (cached)", () => {
64108
for (let i = 0; i < 500; i++) warmed.size();
65109
});

benchmark/cases/compat-source/index.bench.mjs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,19 @@ import sources from "../../../lib/index.js";
1111
import { fixtureCode } from "../../fixtures.mjs";
1212

1313
const fixtureBuffer = Buffer.from(fixtureCode, "utf8");
14+
const fixtureBufferArray = [fixtureBuffer];
1415

1516
const sourceLike = {
1617
source: () => fixtureCode,
1718
buffer: () => fixtureBuffer,
1819
};
1920

21+
const sourceLikeWithBuffers = {
22+
source: () => fixtureCode,
23+
buffer: () => fixtureBuffer,
24+
buffers: () => fixtureBufferArray,
25+
};
26+
2027
const richSourceLike = {
2128
source: () => fixtureCode,
2229
buffer: () => fixtureBuffer,
@@ -56,6 +63,16 @@ export default function register(bench) {
5663
for (let i = 0; i < 500; i++) cs.buffer();
5764
});
5865

66+
bench.add("compat-source: buffers() (fallback via super)", () => {
67+
const cs = new sources.CompatSource({ source: () => fixtureCode });
68+
for (let i = 0; i < 50; i++) cs.buffers();
69+
});
70+
71+
bench.add("compat-source: buffers() (delegated)", () => {
72+
const cs = new sources.CompatSource(sourceLikeWithBuffers);
73+
for (let i = 0; i < 500; i++) cs.buffers();
74+
});
75+
5976
bench.add("compat-source: size() (fallback via super)", () => {
6077
const cs = new sources.CompatSource({ source: () => fixtureCode });
6178
for (let i = 0; i < 50; i++) cs.size();

benchmark/cases/concat-source/index.bench.mjs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,47 @@ export default function register(bench) {
7474
for (let i = 0; i < 10; i++) cs.buffer();
7575
});
7676

77+
bench.add("concat-source: buffers() (10 raw)", () => {
78+
const parts = [];
79+
for (let i = 0; i < 10; i++) parts.push(new sources.RawSource(fixtureCode));
80+
const cs = new sources.ConcatSource(...parts);
81+
for (let i = 0; i < 10; i++) cs.buffers();
82+
});
83+
84+
bench.add("concat-source: buffer() (nested 4x10 raw)", () => {
85+
const makeInner = () => {
86+
const parts = [];
87+
for (let i = 0; i < 10; i++) {
88+
parts.push(new sources.RawSource(fixtureCode));
89+
}
90+
return new sources.ConcatSource(...parts);
91+
};
92+
const cs = new sources.ConcatSource(
93+
makeInner(),
94+
makeInner(),
95+
makeInner(),
96+
makeInner(),
97+
);
98+
for (let i = 0; i < 5; i++) cs.buffer();
99+
});
100+
101+
bench.add("concat-source: buffers() (nested 4x10 raw)", () => {
102+
const makeInner = () => {
103+
const parts = [];
104+
for (let i = 0; i < 10; i++) {
105+
parts.push(new sources.RawSource(fixtureCode));
106+
}
107+
return new sources.ConcatSource(...parts);
108+
};
109+
const cs = new sources.ConcatSource(
110+
makeInner(),
111+
makeInner(),
112+
makeInner(),
113+
makeInner(),
114+
);
115+
for (let i = 0; i < 5; i++) cs.buffers();
116+
});
117+
77118
bench.add("concat-source: size()", () => {
78119
const cs = buildMixed();
79120
for (let i = 0; i < 10; i++) cs.size();

benchmark/cases/realistic-source-map-pipeline/index.bench.mjs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,49 @@ const warmChunk = (() => {
4646
return cached;
4747
})();
4848

49+
/*
50+
* Reproduces the layering pattern called out in
51+
* https://github.com/webpack/webpack-sources/issues/157:
52+
*
53+
* CachedSource -> ConcatSource -> CachedSource -> ConcatSource
54+
*
55+
* At every ConcatSource boundary, the legacy `buffer()` path calls
56+
* Buffer.concat on the children's buffers, which copies all of the bytes
57+
* through each layer. `buffers()` returns Buffer[] without concatenating,
58+
* so the wrapping CachedSource can pass the chunks through to the consumer
59+
* (e.g. fs.createWriteStream / writev) without ever materializing a single
60+
* contiguous Buffer.
61+
*
62+
* Each inner chunk is ~5 raw sources of fixtureCode, the outer chunk stitches
63+
* 4 of them together, mirroring "chunk-of-modules" nesting depth.
64+
*/
65+
/**
66+
* @returns {CachedSource} cached source
67+
*/
68+
function buildLayeredChunk() {
69+
const inner = [];
70+
for (let i = 0; i < 4; i++) {
71+
const parts = [];
72+
for (let j = 0; j < 5; j++) {
73+
parts.push(new sources.RawSource(fixtureCode));
74+
}
75+
inner.push(new sources.CachedSource(new sources.ConcatSource(...parts)));
76+
}
77+
return new sources.CachedSource(
78+
new sources.ConcatSource(
79+
new sources.RawSource("/* outer header */\n"),
80+
...inner,
81+
new sources.RawSource("/* outer footer */\n"),
82+
),
83+
);
84+
}
85+
86+
const warmLayeredChunk = (() => {
87+
const chunk = buildLayeredChunk();
88+
chunk.buffers();
89+
return chunk;
90+
})();
91+
4992
/**
5093
* @param {import("tinybench").Bench} bench bench
5194
*/
@@ -109,4 +152,32 @@ export default function register(bench) {
109152
);
110153
},
111154
);
155+
156+
bench.add(
157+
"realistic-source-map-pipeline: cold buffer() (Cached->Concat->Cached->Concat)",
158+
() => {
159+
buildLayeredChunk().buffer();
160+
},
161+
);
162+
163+
bench.add(
164+
"realistic-source-map-pipeline: cold buffers() (Cached->Concat->Cached->Concat)",
165+
() => {
166+
buildLayeredChunk().buffers();
167+
},
168+
);
169+
170+
bench.add(
171+
"realistic-source-map-pipeline: warm buffer() (Cached->Concat->Cached->Concat)",
172+
() => {
173+
for (let i = 0; i < 50; i++) warmLayeredChunk.buffer();
174+
},
175+
);
176+
177+
bench.add(
178+
"realistic-source-map-pipeline: warm buffers() (Cached->Concat->Cached->Concat)",
179+
() => {
180+
for (let i = 0; i < 50; i++) warmLayeredChunk.buffers();
181+
},
182+
);
112183
}

0 commit comments

Comments
 (0)