Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat($core): Improve build performance with Node's worker threads #2189

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

Conversation

itsxallwater
Copy link

Summary
Related to #1560 and is an alternative take on PR 2163. Implement's Node's worker threads to improve build times for larger scale projects. Includes a breaking change in that you must be using a version of Node (10+) that includes the worker threads module.

What kind of change does this PR introduce? (check at least one)

  • Bugfix
  • Feature
  • Code style update
  • Refactor
  • Docs
  • Build-related changes
  • Other, please describe:

Does this PR introduce a breaking change? (check one)

  • Yes
  • No

It is necessary to upgrade your local install of Node to version 10 or greater, when the worker threads module was introduced.

The PR fulfills these requirements:

  • When resolving a specific issue, it's referenced in the PR's title (e.g. fix #xxx[,#xxx], where "xxx" is the issue number)

You have tested in the following browsers: (Providing a detailed version will be better.)

  • Chrome
  • Firefox
  • Safari
  • Edge
  • IE
  • N/A - Build process change; nothing cosmetic

If adding a new feature, the PR's description includes:

  • A convincing reason for adding this feature
  • Related documents have been updated
  • Related tests have been updated

Other information:
The genesis for this PR goes back to this comment within #1560.

I've got an update to core/lib/node/build/index.js and an addition of core/lib/node/build/worker.js to leverage Node's worker_threads that I've been testing out.

For background, we've got a repo with ~2250 pages (producing a dist of ~4800 files / 350 MB) that was building in 1 hour, 20 minutes with the current release of Vuepress. With these changes in, I'm now getting the following timings:

1x worker thread = 1 hour
2x worker threads = 26 minutes
4x worker threads = 13.5 minutes (ironically the last minute was console updates from workers on what they were rendering, the actual rendering was already done)
8x worker threads = 8.5 minutes (I added in a verbose flag to toggle the console write of the specific document name being rendered)
16x worker threads = 7.5 minutes

Since that comment, I've updated the code to implement the worker thread count as a build option passed to the build command via -w and I've changed the outputs to conform to the build command's --silent option, removing the need for an additional "verbose" flag. If the -w option is not invoked, the process will build with 1x worker thread. The cli documentation was updated in this branch to describe these changes.

The nature of our project has also changed since that comment. We've cleaned up the documentation down to ~1600 pages but we've increased the number of plugins used, including some that can have a net negative impact to build times like Git Log. For the current state of the project, building with 8x worker threads ran for a total of 4 minutes, whereas running with the base VuePress 1.3 version my last build took ~36 hours. I will post some follow up screenshots illustrating the build testing I performed.

itsxallwater and others added 5 commits January 3, 2020 16:24
- Make worker thread count a cli option for build
- Update docs
- Remove verbose mode, use existing silent option
- Make worker thread logger respect silent
@itsxallwater
Copy link
Author

Executing a build of the base VuePress docs w/ 1x worker thread, explicitly requested via CLI.

yarn build -w 1

image

@itsxallwater
Copy link
Author

Executing a build of the base VuePress docs w/ no worker threads specified via CLI.

yarn build

image

@itsxallwater
Copy link
Author

Executing a build of the base VuePress docs w/ 4x worker threads specified via CLI.

yarn build -w 4

image

@itsxallwater
Copy link
Author

Executing a build of the base VuePress docs w/ 8x worker threads and silent mode specified via CLI.

yarn build -w 8 --silent

image

@itsxallwater
Copy link
Author

As one last example, executing a build of our docs w/ 8x worker threads.

image

@itsxallwater
Copy link
Author

Officially requesting your thoughts, @kefranabg, since you've been involved in the conversation on #1560 and have your own PR geared towards addressing this. Apologies if I mucked up anything from a contribution guidelines perspective here, or otherwise! Thanks in advance.

@kefranabg
Copy link
Collaborator

kefranabg commented Feb 18, 2020

@itsxallwater thanks for your PR 👍 Is your project (the one with 1600 pages) public?

When I was doing my tests, I was using a project with 600pages and it lasts 1min30s 🤔

To be honest, what makes VuePress build slow is the intensive use of I/O blocking operations. Node.js’s built-in asynchronous I/O operations are more efficient than Workers can be.

What I'm trying to say, is that I prefer limiting the use of blocking operation in VuePress and replace them with asynchronous operations (which is the best way to gain performances) instead of using workers

@itsxallwater
Copy link
Author

It is, you can check it out at https://github.com/zumasys/docs if you would like.

Your explanation makes total sense. FWIW, testing our build with your latest PR updates resulted in a build time of about 25 minutes.

@kefranabg
Copy link
Collaborator

Awesome. I'll use your repos to made some tests.

I'll close this PR but I referenced your solution as a workaround in the related issue.

@kefranabg kefranabg closed this Feb 23, 2020
@itsxallwater
Copy link
Author

Thanks @kefranabg, I'll be keen to hear how you get along. I'm also happy to take your continued work and test against ours as well. We're building pretty regularly at the moment as we're performing an overall docs migration from another platform, into GitHub + VuePress, so as you might imagine we've got a strong vested interest in tidy build times :)

@a563905150
Copy link

DataCloneError: Data cannot be cloned, out of memory.
at Worker.postMessage (internal/worker.js:251:23)
at Build.render (C:\Users\xwx5326909\Downloads\openeuler-website-v2-master\website-v2\web-ui\node_modules@vuepress\core\lib\node\build\index.js:125:20)
at async App.build (C:\Users\xwx5326909\Downloads\openeuler-website-v2-master\website-v2\web-ui\node_modules@vuepress\core\lib\node\App.js:499:5)

My project has about 1500 pages, and I updated it index.js , added worker.js But there was such a error.

@itsxallwater
Copy link
Author

itsxallwater commented Oct 26, 2020

@a563905150 did your system truly run out of memory? If so you can limit the memory usage using this command as you build script:

node --max_old_space_size=8192 ./node_modules/vuepress/cli.js build src

Change 8192 to a value that makes more sense for your system.

@a563905150
Copy link

@itsxallwater I did, but I still reported an error

@itsxallwater
Copy link
Author

What version of node are you running?

@a563905150
Copy link

@itsxallwater 12.16.1,I found a Worker threads configuration,resourceLimits,https://nodejs.org/docs/latest-v12.x/api/worker_threads.html,but it didn't work

@itsxallwater
Copy link
Author

Hmm, I'm rolling 12.14. Do you have a repo I can test against? Have you tried it away from your current environment, perhaps in a CI/CD runner?

@a563905150
Copy link

@itsxallwater https://github.com/a563905150/openeuler,you can try my repo

@itsxallwater
Copy link
Author

@a563905150 It built for me, although two of the docs appear to have some issues that would need to be resolved. Here's the build output:

wait Rendering static HTML...
wait Worker #0 beginning rendering of 155 pages
wait Worker #0 has rendered 50 of 155 pages
wait Worker #1 beginning rendering of 156 pages
wait Worker #1 has rendered 50 of 156 pages
wait Worker #2 beginning rendering of 156 pages
error Worker #2 error rendering /docs/en/other/legal/: false
undefined
(node:1172) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'security' of undefined
    at a.render (docs/en/other/legal/README.md?52cb:1:211)
    at a.t._render (/home/mikew/src/Temp/openeuler/node_modules/vue/dist/vue.runtime.common.prod.js:6:35273)
    at /home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:70575
    at Yi (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:67139)
    at io (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:70551)
    at ro (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:70182)
    at eo (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:67429)
    at /home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:70649
    at Yi (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:67139)
    at io (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:70551)
    at ro (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:70182)
    at _t.eo [as renderNode] (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:67429)
    at _t.next (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:20445)
    at n (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:18657)
    at /home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:67250
    at _t.eo [as renderNode] (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:67401)
(node:1172) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:1172) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
wait Worker #3 beginning rendering of 156 pages
wait Worker #4 beginning rendering of 156 pages
wait Worker #5 beginning rendering of 156 pages
error Worker #2 sent exit code: 1 false
wait Worker #0 has rendered 100 of 155 pages
wait Worker #6 beginning rendering of 156 pages
wait Worker #3 has rendered 50 of 156 pages
wait Worker #7 beginning rendering of 156 pages
wait Worker #1 has rendered 100 of 156 pages
wait Worker #4 has rendered 50 of 156 pages
wait Worker #5 has rendered 50 of 156 pages
wait Worker #0 has rendered 150 of 155 pages
wait Worker #0 has rendered 155 of 155 pages
success Worker 0 completed successfully.
wait Worker #3 has rendered 100 of 156 pages
wait Worker #6 has rendered 50 of 156 pages
wait Worker #4 has rendered 100 of 156 pages
wait Worker #1 has rendered 150 of 156 pages
wait Worker #7 has rendered 50 of 156 pages
wait Worker #1 has rendered 156 of 156 pages
success Worker 1 completed successfully.
wait Worker #5 has rendered 100 of 156 pages
wait Worker #3 has rendered 150 of 156 pages
wait Worker #6 has rendered 100 of 156 pages
wait Worker #3 has rendered 156 of 156 pages
success Worker 3 completed successfully.
wait Worker #4 has rendered 150 of 156 pages
wait Worker #4 has rendered 156 of 156 pages
success Worker 4 completed successfully.
wait Worker #7 has rendered 100 of 156 pages
wait Worker #5 has rendered 150 of 156 pages
wait Worker #5 has rendered 156 of 156 pages
success Worker 5 completed successfully.
wait Worker #6 has rendered 150 of 156 pages
wait Worker #6 has rendered 156 of 156 pages
success Worker 6 completed successfully.
error Worker #7 error rendering /docs/zh/other/legal/: false
undefined
(node:1172) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'security' of undefined
    at a.render (docs/zh/other/legal/README.md?8057:1:211)
    at a.t._render (/home/mikew/src/Temp/openeuler/node_modules/vue/dist/vue.runtime.common.prod.js:6:35273)
    at /home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:70575
    at Yi (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:67139)
    at io (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:70551)
    at ro (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:70182)
    at eo (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:67429)
    at /home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:70649
    at Yi (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:67139)
    at io (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:70551)
    at ro (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:70182)
    at _t.eo [as renderNode] (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:67429)
    at _t.next (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:20445)
    at n (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:18657)
    at /home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:67250
    at _t.eo [as renderNode] (/home/mikew/src/Temp/openeuler/node_modules/vue-server-renderer/build.prod.js:1:67401)
(node:1172) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:1172) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
error Worker #7 sent exit code: 1 false
success Generated static files in .vuepress/dist.

My addWorkerScript.js and updateBuildScript.js are the same as yours with one exception: line 27 of addWorkerScript.js, I have true instead of false on the right side of that or.

I tested the build out by adding a new script to the package.json file called new-build:

  "scripts": {
    "dev": "vuepress dev docs",
    "build": "node --max_old_space_size=9216 node_modules/vuepress/cli.js build docs",
    "new-build": "script/build.sh"
  },

And that build.sh file looks like this:

#!/bin/sh
cp script/updateBuildScript.js node_modules/@vuepress/core/lib/node/build/index.js
cp script/addWorkerScript.js node_modules/@vuepress/core/lib/node/build/worker.js
vuepress build

Environment wise I ran this in an Ubuntu instance of WSL 2.0.

@a563905150
Copy link

@itsxallwater Now that it works, the build has been shortened from 37 minutes to 8 minutes, thank you very much!

@itsxallwater
Copy link
Author

My pleasure!

@sr2ds
Copy link

sr2ds commented Apr 13, 2023

Hello guys, somebody working with this cases?

I'm trying to improve and fixes my cases with vuepress, my blog has +2K posts and I can´t do build now.
I tried with 10GB on max_old_space_size and I try change the max-concurrency limited as well, but without success.

On my tests, I tried change some behaviors to try prevent big memory allocation, like set as null some variable missed, split some code to new function to help GC work, but I'm not a performance specialist, then was only random tries.

I dont sure but I guess that the renderer can be the issue about the big memory allocation without cleanup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants