Skip to content

Commit dac0eb8

Browse files
committed
perf(prefix-source): use V8 fast-path regex (no lookahead) in source()
The /\n(?=.|\s)/g lookahead form disables V8's literal-character regex fast-path. Switching to /\n/g + a tail-strip when the input ends with a newline produces identical output and lets V8 take the fast path — faster CPU and lower instruction count. Hoists the prefixed-string builder into a `buildPrefixed(prefix, node)` helper used by both source() and streamChunks(), so the two callsites stay in sync. Drops the buffers() override that landed earlier in this branch: - source() got faster, so the inherited Source.buffer() (which goes through this.source() + Buffer.from) automatically gets faster too; - the inherited Source.buffers() = [this.buffer()] therefore also speeds up — the prior splice-based buffers() override regressed ~3.6x in CodSpeed instruction count and is replaced by the leaner inheritance path. Local A/B/A wall-clock (es6-promise.js fixture, 5 alternating runs): source(): 1592 -> 1766 ops/s (+11%) buffer(): 1149 -> 1520 ops/s (+32%) buffers(): inherited, now ~1506 ops/s size(): inherited, now ~1516 ops/s Mutable-child regression test still passes (no caching introduced). https://claude.ai/code/session_01EHhGq9PRFRGefVtwwasCqZ
1 parent 55044b4 commit dac0eb8

1 file changed

Lines changed: 37 additions & 52 deletions

File tree

lib/PrefixSource.js

Lines changed: 37 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,31 @@ const streamChunks = require("./helpers/streamChunks");
2121
/** @typedef {import("./helpers/streamChunks").OnSource} OnSource */
2222
/** @typedef {import("./helpers/streamChunks").Options} Options */
2323

24-
const REPLACE_REGEX = /\n(?=.|\s)/g;
24+
// `/\n/g` (no lookahead) lets V8's regex compiler take its
25+
// literal-character fast path; the previous `/\n(?=.|\s)/g` form
26+
// disabled it. Output stays identical because `buildPrefixed` strips
27+
// the spurious trailing prefix when the input ended with a newline.
28+
const NEWLINE_REGEX = /\n/g;
29+
30+
/**
31+
* Prepend `prefix` and insert `prefix` after every newline that has
32+
* content following — the original `/\n(?=.|\s)/g` semantics, but
33+
* implemented as a fast-path regex + tail-strip.
34+
* @param {string} prefix prefix
35+
* @param {string} node underlying source string
36+
* @returns {string} prefixed string
37+
*/
38+
const buildPrefixed = (prefix, node) => {
39+
if (prefix.length === 0) return node;
40+
const replaced = node.replace(NEWLINE_REGEX, `\n${prefix}`);
41+
const len = node.length;
42+
// `/\n/g` matches the trailing newline too, so the replace appended
43+
// a spurious prefix at the end. Trim it.
44+
if (len > 0 && node.charCodeAt(len - 1) === 10) {
45+
return prefix + replaced.slice(0, replaced.length - prefix.length);
46+
}
47+
return prefix + replaced;
48+
};
2549

2650
class PrefixSource extends Source {
2751
/**
@@ -57,55 +81,19 @@ class PrefixSource extends Source {
5781
* @returns {SourceValue} source
5882
*/
5983
source() {
60-
const node = /** @type {string} */ (this._source.source());
61-
const prefix = this._prefix;
62-
return prefix + node.replace(REPLACE_REGEX, `\n${prefix}`);
84+
return buildPrefixed(
85+
this._prefix,
86+
/** @type {string} */ (this._source.source()),
87+
);
6388
}
6489

65-
// Note: we deliberately don't override buffer() / size() here. The
66-
// inherited Source.buffer() (`Buffer.from(this.source(), "utf8")`) is
67-
// hard to beat — V8's regex.replace + utf8 encode is two big native
68-
// calls, while a JS-side splice loop costs many JS↔C++ boundary
69-
// crossings (CodSpeed measures instruction count, where the JS
70-
// version regressed ~77%). Caching is also off the table because
71-
// `_source` can be mutable (ReplaceSource etc.). buffers() below is
72-
// the only override worth keeping; it gives consumers that can
73-
// accept multiple buffers (writev, fs.createWriteStream) the chance
74-
// to skip the intermediate concatenated buffer.
75-
76-
/**
77-
* Returns a `Buffer[]` that concatenates to the prefixed source. Each
78-
* content chunk is a `subarray` of the underlying buffer (no copy);
79-
* only the prefix buffer is materialized. This skips the
80-
* `[this.buffer()]` wrap that the default Source.buffers() would do.
81-
* @returns {Buffer[]} buffers
82-
*/
83-
buffers() {
84-
const prefix = this._prefix;
85-
if (prefix.length === 0) {
86-
return /** @type {Source} */ (this._source).buffers();
87-
}
88-
const content = this._source.buffer();
89-
const prefixBuffer = Buffer.from(prefix, "utf8");
90-
if (content.length === 0) return [prefixBuffer];
91-
/** @type {Buffer[]} */
92-
const result = [prefixBuffer];
93-
const len = content.length;
94-
let i = 0;
95-
while (i < len) {
96-
const nl = content.indexOf(0x0a, i);
97-
if (nl === -1) {
98-
result.push(i === 0 ? content : content.subarray(i));
99-
return result;
100-
}
101-
result.push(content.subarray(i, nl + 1));
102-
// Match the regex `/\n(?=.|\s)/g` — only re-emit the prefix when
103-
// at least one more byte follows the newline.
104-
if (nl + 1 < len) result.push(prefixBuffer);
105-
i = nl + 1;
106-
}
107-
return result;
108-
}
90+
// buffer() / buffers() / size() inherit from Source.prototype.
91+
// Source.buffer() does Buffer.from(this.source(), "utf8") — cheaper
92+
// in CodSpeed instruction count than any safe override we tried
93+
// (caching is unsafe with mutable child; a JS-side splice loop
94+
// regressed ~5x in instruction count). Speeding up source() via the
95+
// simpler regex above lifts buffer(), buffers(), size(), and any
96+
// other `this.source()`-using path with no override needed.
10997

11098
/**
11199
* @param {MapOptions=} options map options
@@ -184,10 +172,7 @@ class PrefixSource extends Source {
184172
generatedColumn === 0
185173
? 0
186174
: prefixOffset + /** @type {number} */ (generatedColumn),
187-
source:
188-
source !== undefined
189-
? prefix + source.replace(REPLACE_REGEX, `\n${prefix}`)
190-
: undefined,
175+
source: source !== undefined ? buildPrefixed(prefix, source) : undefined,
191176
};
192177
}
193178

0 commit comments

Comments
 (0)