Skip to content

incremental rebuilds of Node.js are slow #47984

Closed as not planned
Closed as not planned
@kvakil

Description

@kvakil

What is the problem this feature will solve?

Incremental rebuilds of Node.js are slow. This is annoying for developers, especially those with less powerful machines.

I am fortunate enough to have a very powerful machine, and it's still pretty slow for me. It takes 22 seconds to do an incremental rebuild where I've changed a single Javascript file:

$ TERM=dumb ninja -j 1 -C out/Release node | ts -i '%.s'
0.020594 ninja: Entering directory `out/Release'
1.410648 [1/12] ACTION libnode: node_js2c_1613b577312726d6acf41fb14bd20601
0.001637 [2/12] STAMP obj/libnode.actions_rules_copies.stamp
9.011218 [3/12] CXX obj/gen/libnode.node_javascript.o
0.101600 [4/12] AR obj/libnode.a
0.001407 [5/12] STAMP obj/node_mksnapshot.compile_depends.stamp
0.001513 [6/12] STAMP obj/node_mksnapshot.actions_depends.stamp
2.838031 [7/12] LINK node_mksnapshot
0.001629 [8/12] STAMP obj/node.actions_depends.stamp
0.283595 [9/12] ACTION node: node_mksnapshot_9b7a2d2290b02e76d66661df74749f56
0.001580 [10/12] STAMP obj/node.actions_rules_copies.stamp
5.806897 [11/12] CXX obj/gen/node.node_snapshot.o
2.939010 [12/12] LINK node

What is the feature you are proposing to solve the problem?

Not sure, some ideas:

For gen/node_javascript.cc and gen/node_snapshot.cc, we have a bunch of large generated C++ array literals, which is known to be slow. Currently compiling those two files takes 9s + 6s ~ 15s on my powerful machine. I hacked together a version which makes js2c and node_mksnapshot generate the data via inline assembly, and it made that incremental recompile ~2x as fast (12s):

$ TERM=dumb ninja -j 1 -C out/Release node | ts -i '%.s'
0.020747 ninja: Entering directory `out/Release'
0.213469 [1/12] ACTION libnode: node_js2c_1613b577312726d6acf41fb14bd20601
0.001563 [2/12] STAMP obj/libnode.actions_rules_copies.stamp
3.576886 [3/12] CXX obj/gen/libnode.node_javascript.o
0.102211 [4/12] AR obj/libnode.a
0.002206 [5/12] STAMP obj/node_mksnapshot.compile_depends.stamp
0.001503 [6/12] STAMP obj/node_mksnapshot.actions_depends.stamp
2.811231 [7/12] LINK node_mksnapshot
0.001377 [8/12] STAMP obj/node.actions_depends.stamp
0.191197 [9/12] ACTION node: node_mksnapshot_9b7a2d2290b02e76d66661df74749f56
0.001393 [10/12] STAMP obj/node.actions_rules_copies.stamp
2.070298 [11/12] CXX obj/gen/node.node_snapshot.o
2.889826 [12/12] LINK node

I plan to upstream this change, at least for gcc/Linux. However this is platform-dependent & we'd likely need different inline assembly for each platform (or use an existing tool that abstracts this, like incbin.h).

We could try having postject inject the js2c files/snapshot data. You could also imagine building a single node executable rather than node_mksnapshot/node, and using postject to inject "hydrate" an existing node with the generated snapshot data. This could avoid doing the linking twice. It would also let us centralize on a single path for snapshot deserialization, however it may regress startup performance/memory as we'd need to parse the snapshot into our in-memory format.

And perhaps -fvisibility=hidden would help with link times? Not sure.

What alternatives have you considered?

There are some ways to improve incremental rebuild time for specific sorts of changes (like --node-builtin-modules-path or ccache), but ideally it wouldn't be slow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    buildIssues and PRs related to build files or the CI.feature requestIssues that request new features to be added to Node.js.snapshotIssues and PRs related to the startup snapshotstale

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions