-
Notifications
You must be signed in to change notification settings - Fork 29.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
loader: speed up line length calculation used by moduleProvider #50969
loader: speed up line length calculation used by moduleProvider #50969
Conversation
172138f
to
686ce4e
Compare
Pinging the modules team since the loaders WG readme doesn't mention who is in it (apologies if you aren't working on loaders but got pinged here 🙏🏽 ). |
Next time please just ping @nodejs/loaders. cc @nodejs/performance |
Ah so those teams do exist! I assumed they didn't as the autocomplete here didn't show them. 🤦🏽♂️ |
// account in coverage calculations. | ||
// codepoints for \n, \u2028 and \u2029 | ||
if (codePoint === 10 || codePoint === 0x2028 || codePoint === 0x2029) { | ||
output.push(lineLength); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
output.push(lineLength); | |
ArrayPrototypePush(output, lineLength); |
lineLength = -1; // To not count the matched codePoint such as \n character | ||
} | ||
} | ||
output.push(lineLength); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
output.push(lineLength); | |
ArrayPrototypePush(output, lineLength); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ArrayPrototypePush has performance implications according to primordials.md
. Since adding primordials is optional, I'm strongly against adding it for array.prototype.push
in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for source map support, we don't want user code to be able to mess with internal implementation – i.e. we should take the potential performance penalty and generate reliably correct stack traces. Also, we should measure the perf difference, as it might be negligable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alternatively, output[output.length] = lineLength
?
lineLength = -1; // To not count the matched codePoint such as \n character | ||
} | ||
} | ||
output.push(lineLength); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
output.push(lineLength); | |
ArrayPrototypePush(output, lineLength); |
4e8a83f
to
813a604
Compare
// account in coverage calculations. | ||
// codepoints for \n (new line), \u2028 (line separator) and \u2029 (paragraph separator) | ||
if (codePoint === 10 || codePoint === 0x2028 || codePoint === 0x2029) { | ||
output.push(lineLength) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
output.push(lineLength) | |
ArrayPrototypePush(output, lineLength); |
// account in coverage calculations. | ||
// codepoints for \n (new line), \u2028 (line separator) and \u2029 (paragraph separator) | ||
if (codePoint === 10 || codePoint === 0x2028 || codePoint === 0x2029) { | ||
output.push(lineLength) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the non-primordial version due to ArrayPrototypePush
being listed as having perf issues — https://github.com/nodejs/node/blob/main/doc/contributing/primordials.md#primordials-with-known-performance-issues
lineLength = -1; // To not count the matched codePoint such as \n character | ||
} | ||
} | ||
output.push(lineLength) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
output.push(lineLength) | |
ArrayPrototypePush(output, lineLength); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ArrayPrototypePush
has known perf issues — #50969 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using .push
has reliability issues, as user code could affect internals.
813a604
to
133f887
Compare
@aduh95 @GeoffreyBooth @anonrig What's the call on the usage of |
@zeusdeux Primordials are optional and not mandatory for new pull requests. It's your decision. Documentation says that:
Ref: https://github.com/nodejs/node/blob/main/doc/contributing/primordials.md |
I don’t have a preference. I thought we were still debating whether they should be required, but I don’t really see the need. |
When using a loader, for say TypeScript, the esm loader invokes the `lineLengths` function via `maybeCacheSourceMap` when sourcemaps are enabled. Therefore, `lineLengths` ends up getting called quite often when running large servers written in TypeScript for example. Making `lineLengths` faster should therefore speed up server startup times for anyone using a loader with node with sourcemaps enabled. The change itself is fairly simple and is all about removing creation of unnecessary memory and iterating the whole source content only once with the hope of making the function cache friendly.
364939f
to
51f44ab
Compare
If you're going to use that one you might as well use the others too? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks great, robustness > performance, altho even better if we can get both.
you may want to benchmark arr[arr.length] = x
over ArrayPrototypePush(arr, x)
just in case tho
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great to hear we can reconcile robustness and perf. Ideally we would run benchmarks after each V8 upgrades to see if the claims in primordials.md
are still correct, but it's very time consuming, and easy to get false positive.
If you want to check the performance of arr[arr.length] = value
and setOwnProperty(arr, arr.length, value)
for completeness, that'd be useful data.
@GeoffreyBooth which other ones? This PR uses |
Sure, whatever works for you. But FYI this PR needs to wait at least 48 hours after it was opened before we can merge it to give time for folks to review, so it may be quicker to get the data before the PR is merged depending on what are your availabilities. |
Commit Queue failed- Loading data for nodejs/node/pull/50969 ✔ Done loading data for nodejs/node/pull/50969 ----------------------------------- PR info ------------------------------------ Title loader: speed up line length calculation used by moduleProvider (#50969) Author Mudit (@zeusdeux) Branch zeusdeux:speed-up-source-map-processing -> nodejs:main Labels module, performance, needs-ci, source maps Commits 1 - loader: speed up line length calc used by moduleProvider Committers 1 - Mudit Ameta PR-URL: https://github.com/nodejs/node/pull/50969 Reviewed-By: Yagiz Nizipli Reviewed-By: Geoffrey Booth Reviewed-By: Antoine du Hamel ------------------------------ Generated metadata ------------------------------ PR-URL: https://github.com/nodejs/node/pull/50969 Reviewed-By: Yagiz Nizipli Reviewed-By: Geoffrey Booth Reviewed-By: Antoine du Hamel -------------------------------------------------------------------------------- ℹ This PR was created on Wed, 29 Nov 2023 22:02:05 GMT ✔ Approvals: 3 ✔ - Yagiz Nizipli (@anonrig) (TSC): https://github.com/nodejs/node/pull/50969#pullrequestreview-1758724448 ✔ - Geoffrey Booth (@GeoffreyBooth) (TSC): https://github.com/nodejs/node/pull/50969#pullrequestreview-1756426036 ✔ - Antoine du Hamel (@aduh95) (TSC): https://github.com/nodejs/node/pull/50969#pullrequestreview-1757049826 ✔ Last GitHub CI successful ℹ Last Full PR CI on 2023-11-29T22:54:10Z: https://ci.nodejs.org/job/node-test-pull-request/56011/ ⚠ Commits were pushed after the last Full PR CI run: ⚠ - loader: speed up line length calc used by moduleProvider - Querying data for job/node-test-pull-request/56011/ ✔ Last Jenkins CI successful -------------------------------------------------------------------------------- ✔ Aborted `git node land` session in /home/runner/work/node/node/.ncuhttps://github.com/nodejs/node/actions/runs/7065693910 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huzzah 🙌 Thanks for this!
Out of curiosity, where did the huge perf gain come from? arr.map
→ for
, or RegExp → equality assertion, or both significantly contributed? (I would guess both)
Landed in d5a7acf |
@JakobJingleheimer Thanks! The speed up was from the combination of the following —
Basically doing only what's needed and doing it using an known optimizable construct. Finally, no extra memory overhead than absolutely necessary. Thinking in C rather than JS in a way. Functional programming is good but it's usually horrible for perf so going old school for parts that are sensitive to perf is always a win! |
When using a loader, for say TypeScript, the esm loader invokes the `lineLengths` function via `maybeCacheSourceMap` when sourcemaps are enabled. Therefore, `lineLengths` ends up getting called quite often when running large servers written in TypeScript for example. Making `lineLengths` faster should therefore speed up server startup times for anyone using a loader with node with sourcemaps enabled. The change itself is fairly simple and is all about removing creation of unnecessary memory and iterating the whole source content only once with the hope of making the function cache friendly. PR-URL: #50969 Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io> Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br> Reviewed-By: Jacob Smith <jacob@frende.me>
When using a loader, for say TypeScript, the esm loader invokes the `lineLengths` function via `maybeCacheSourceMap` when sourcemaps are enabled. Therefore, `lineLengths` ends up getting called quite often when running large servers written in TypeScript for example. Making `lineLengths` faster should therefore speed up server startup times for anyone using a loader with node with sourcemaps enabled. The change itself is fairly simple and is all about removing creation of unnecessary memory and iterating the whole source content only once with the hope of making the function cache friendly. PR-URL: #50969 Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io> Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br> Reviewed-By: Jacob Smith <jacob@frende.me>
When using a loader, for e.g., say TypeScript, the esm loader invokes the
lineLengths
function viamaybeCacheSourceMap
when sourcemaps are enabled. Therefore,lineLengths
ends up getting called quite often when running large servers written in TypeScript for example. MakinglineLengths
faster should therefore speed up server startup times for anyone using a loader with node with sourcemaps enabled.The change itself is fairly simple and is all about removing creation of unnecessary memory and iterating the whole source content only once with the hope of making the function cache friendly.
make -j4 test
passesAlongside that, below are the
cpuprofile
s of node 20.9.0 vs nodemain
with this patch taken during the startup of large server (from work) written in TypeScript loaded using esno (a proxy to esbuild-register) showing a ~98% reduction in time taken bymoduleProvider
.node 20.9.0
node
main
+ patch from this PR