-
-
Notifications
You must be signed in to change notification settings - Fork 33.9k
loader: speed up line length calculation used by moduleProvider #50969
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
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? |
ljharb
left a 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.
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
aduh95
left a 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.
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
lineLengthsfunction viamaybeCacheSourceMapwhen sourcemaps are enabled. Therefore,lineLengthsends up getting called quite often when running large servers written in TypeScript for example. MakinglineLengthsfaster 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 testpassesAlongside that, below are the
cpuprofiles of node 20.9.0 vs nodemainwith 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