-
Notifications
You must be signed in to change notification settings - Fork 350
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
perf: avoid excessive linear scanning for large functions #1399
Open
JoostK
wants to merge
2
commits into
benjamn:master
Choose a base branch
from
JoostK:perf/clone-tree
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This commit restores the token indices for a node after they have been refined to span the node, instead of before the refinement. This avoids the potential for excessive searches for token indices, which is implemented using a linear scan. It was attempted to convert the linear scans into binary searches (which an accompanying comment on `findTokenRange` indicates shouldn't be needed) and that also avoids the problem, but using the refined positions as implemented in this commit is even more effective, to the point where using a binary search is less efficient in my testing. This change can achieve a substantial improvement, where an ~2.6MB file used to be parsed and cloned in ~120s, now reduced to ~1.5s, almost two orders of magnitude faster. The perf test that tests the `backbone.js` file shows that performance improves from ~60ms to ~53ms, only a slight improvement.
This commit makes various micro optimizations to improve node cloning performance. In particular, `Object.create` with property descriptors appears to be slower than `Object.defineProperty` after the object has been created (measured in Node 18 and 20). The other bigger factor was the early bailout in `TCp.copy` for non-object values, as such primitives will never hit any of the remaining logic. For a 2.6MB large file, this improves parsing and cloning times from ~1.5s to ~1.2s. The performance test using `backbone.js` sess about 10% improvement.
JoostK
added a commit
to JoostK/cypress
that referenced
this pull request
May 20, 2024
This change is in addition to benjamn/recast#1399. This commit focuses on Cypress' use of recast, with the following optimizations: 1. Avoid printing the source file again if no change was made. Printing an AST using recast does reuse the original text, but identifying for which parts of the AST reuse is possible comes with noticeable overhead. By tracking changes within the visitor it becomes possible to skip this phase entirely if no changes were made. 2. Avoid a scope lookup (`path.scope.declares(node.name)`) for all identifiers in the program, doing it only for identifiers that may have to be rewritten. With these changes, a 2.6MB bundle that does not need rewriting has improved from 4.4s to 2.3s, provided that the above-mentioned recast PR has been applied.
This was referenced May 20, 2024
JoostK
added a commit
to JoostK/cypress
that referenced
this pull request
May 20, 2024
This change is in addition to benjamn/recast#1399. This commit focuses on Cypress' use of recast, with the following optimizations: 1. Avoid printing the source file again if no change was made. Printing an AST using recast does reuse the original text, but identifying for which parts of the AST reuse is possible comes with noticeable overhead. By tracking changes within the visitor it becomes possible to skip this phase entirely if no changes were made. 2. Avoid a scope lookup (`path.scope.declares(node.name)`) for all identifiers in the program, doing it only for identifiers that may have to be rewritten. With these changes, a 2.6MB bundle that does not need rewriting has improved from 4.4s to 2.3s, provided that the above-mentioned recast PR has been applied.
JoostK
added a commit
to JoostK/cypress
that referenced
this pull request
May 20, 2024
This change is in addition to benjamn/recast#1399. This commit focuses on Cypress' use of recast, with the following optimizations: 1. Avoid printing the source file again if no change was made. Printing an AST using recast does reuse the original text, but identifying for which parts of the AST reuse is possible comes with noticeable overhead. By tracking changes within the visitor it becomes possible to skip this phase entirely if no changes were made. 2. Avoid a scope lookup (`path.scope.declares(node.name)`) for all identifiers in the program, doing it only for identifiers that may have to be rewritten. With these changes, a 2.6MB bundle that does not need rewriting has improved from 4.4s to 2.3s, provided that the above-mentioned recast PR has been applied.
jennifer-shehane
added a commit
to cypress-io/cypress
that referenced
this pull request
Jun 21, 2024
* perf: improve performance of `experimentalSourceRewriting` This change is in addition to benjamn/recast#1399. This commit focuses on Cypress' use of recast, with the following optimizations: 1. Avoid printing the source file again if no change was made. Printing an AST using recast does reuse the original text, but identifying for which parts of the AST reuse is possible comes with noticeable overhead. By tracking changes within the visitor it becomes possible to skip this phase entirely if no changes were made. 2. Avoid a scope lookup (`path.scope.declares(node.name)`) for all identifiers in the program, doing it only for identifiers that may have to be rewritten. With these changes, a 2.6MB bundle that does not need rewriting has improved from 4.4s to 2.3s, provided that the above-mentioned recast PR has been applied. * chore: move `experimentalSourceRewriting` release note to pending release section --------- Co-authored-by: Bill Glesias <[email protected]> Co-authored-by: Jennifer Shehane <[email protected]>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This commit restores the token indices for a node after they have been refined
to span the node, instead of before the refinement. This avoids the potential for
excessive searches for token indices, which is implemented using a linear scan.
It was attempted to convert the linear scans into binary searches (which an
accompanying comment on
findTokenRange
indicates shouldn't be needed) and thatalso avoids the problem, but using the refined positions as implemented in this
commit is even more effective, to the point where using a binary search is less
efficient in my testing.
This change can achieve a substantial improvement, where an ~2.6MB file used to be
parsed and cloned in ~120s, now reduced to ~1.5s, almost two orders of magnitude
faster.
The perf test that tests the
backbone.js
file shows that performance improvesfrom ~60ms to ~53ms, only a slight improvement.
The offending file I used for testing was ElkJS bundled into a single file: elkjs.js.zip. It used to take ~120s on a MacBook Pro M1 Max to clone the parsed tree, which has now been reduced to 1.2s for a 100x improvement.