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: Binary search in token store utils.search
#17066
Conversation
✅ Deploy Preview for docs-eslint ready!
To edit notification comments on pull requests, go to your Netlify site settings. |
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.
LGTM. Really nice work! Leaving open for @mdjermanovic to verify his feedback was addressed.
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.
LGTM, thanks!
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [eslint](https://eslint.org) ([source](https://github.com/eslint/eslint)) | devDependencies | minor | [`8.37.0` -> `8.39.0`](https://renovatebot.com/diffs/npm/eslint/8.37.0/8.39.0) | --- ### Release Notes <details> <summary>eslint/eslint</summary> ### [`v8.39.0`](https://github.com/eslint/eslint/releases/tag/v8.39.0) [Compare Source](eslint/eslint@v8.38.0...v8.39.0) #### Features - [`3f7af9f`](eslint/eslint@3f7af9f) feat: Implement `SourceCode#markVariableAsUsed()` ([#​17086](eslint/eslint#17086)) (Nicholas C. Zakas) #### Documentation - [`6987dc5`](eslint/eslint@6987dc5) docs: Fix formatting in Custom Rules docs ([#​17097](eslint/eslint#17097)) (Milos Djermanovic) - [`4ee92e5`](eslint/eslint@4ee92e5) docs: Update README (GitHub Actions Bot) - [`d8e9887`](eslint/eslint@d8e9887) docs: Custom Rules cleanup/expansion ([#​16906](eslint/eslint#16906)) (Ben Perlmutter) - [`1fea279`](eslint/eslint@1fea279) docs: Clarify how to add to tsc agenda ([#​17084](eslint/eslint#17084)) (Nicholas C. Zakas) - [`970ef1c`](eslint/eslint@970ef1c) docs: Update triage board location (Nicholas C. Zakas) - [`6d8bffd`](eslint/eslint@6d8bffd) docs: Update README (GitHub Actions Bot) #### Chores - [`60a6f26`](eslint/eslint@60a6f26) chore: upgrade [@​eslint/js](https://github.com/eslint/js)[@​8](https://github.com/8).39.0 ([#​17102](eslint/eslint#17102)) (Milos Djermanovic) - [`d5ba5c0`](eslint/eslint@d5ba5c0) chore: package.json update for [@​eslint/js](https://github.com/eslint/js) release (ESLint Jenkins) - [`f57eff2`](eslint/eslint@f57eff2) ci: run tests on Node.js v20 ([#​17093](eslint/eslint#17093)) (Nitin Kumar) - [`9d1b8fc`](eslint/eslint@9d1b8fc) perf: Binary search in token store `utils.search` ([#​17066](eslint/eslint#17066)) (Francesco Trotta) - [`07a4435`](eslint/eslint@07a4435) chore: Add request for minimal repro to bug report ([#​17081](eslint/eslint#17081)) (Nicholas C. Zakas) - [`eac4943`](eslint/eslint@eac4943) refactor: remove unnecessary use of `SourceCode#getAncestors` in rules ([#​17075](eslint/eslint#17075)) (Milos Djermanovic) - [`0a7b60a`](eslint/eslint@0a7b60a) chore: update description of `SourceCode#getDeclaredVariables` ([#​17072](eslint/eslint#17072)) (Milos Djermanovic) - [`6e2df71`](eslint/eslint@6e2df71) chore: remove unnecessary references to the LICENSE file ([#​17071](eslint/eslint#17071)) (Milos Djermanovic) ### [`v8.38.0`](https://github.com/eslint/eslint/releases/tag/v8.38.0) [Compare Source](eslint/eslint@v8.37.0...v8.38.0) #### Features - [`a1d561d`](eslint/eslint@a1d561d) feat: Move getDeclaredVariables and getAncestors to SourceCode ([#​17059](eslint/eslint#17059)) (Nicholas C. Zakas) #### Bug Fixes - [`1c1ece2`](eslint/eslint@1c1ece2) fix: do not report on `RegExp(...args)` in `require-unicode-regexp` ([#​17037](eslint/eslint#17037)) (Francesco Trotta) #### Documentation - [`7162d34`](eslint/eslint@7162d34) docs: Mention new config system is complete ([#​17068](eslint/eslint#17068)) (Nicholas C. Zakas) - [`0fd6bb2`](eslint/eslint@0fd6bb2) docs: Update README (GitHub Actions Bot) - [`c83531c`](eslint/eslint@c83531c) docs: Update/remove external links, eg. point to `eslint-community` ([#​17061](eslint/eslint#17061)) (Pelle Wessman) - [`a3aa6f5`](eslint/eslint@a3aa6f5) docs: Clarify `no-div-regex` rule docs ([#​17051](eslint/eslint#17051)) (Francesco Trotta) - [`b0f11cf`](eslint/eslint@b0f11cf) docs: Update README (GitHub Actions Bot) - [`da8d52a`](eslint/eslint@da8d52a) docs: Update the second object instance for the "no-new" rule ([#​17020](eslint/eslint#17020)) (Ahmadou Waly NDIAYE) - [`518130a`](eslint/eslint@518130a) docs: switch language based on current path ([#​16687](eslint/eslint#16687)) (Percy Ma) - [`24206c4`](eslint/eslint@24206c4) docs: Update README (GitHub Actions Bot) #### Chores - [`59ed060`](eslint/eslint@59ed060) chore: upgrade [@​eslint/js](https://github.com/eslint/js)[@​8](https://github.com/8).38.0 ([#​17069](eslint/eslint#17069)) (Milos Djermanovic) - [`88c0898`](eslint/eslint@88c0898) chore: package.json update for [@​eslint/js](https://github.com/eslint/js) release (ESLint Jenkins) - [`cf682d2`](eslint/eslint@cf682d2) refactor: simplify new-parens rule schema ([#​17060](eslint/eslint#17060)) (MHO) - [`0dde022`](eslint/eslint@0dde022) ci: bump actions/add-to-project from 0.4.1 to 0.5.0 ([#​17055](eslint/eslint#17055)) (dependabot\[bot]) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS4zNS4xIiwidXBkYXRlZEluVmVyIjoiMzUuNTUuMSJ9--> Co-authored-by: cabr2-bot <cabr2.help@gmail.com> Reviewed-on: https://codeberg.org/Calciumdibromid/CaBr2/pulls/1852 Reviewed-by: Epsilon_02 <epsilon_02@noreply.codeberg.org> Co-authored-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org> Co-committed-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org>
Prerequisites checklist
What is the purpose of this pull request? (put an "X" next to an item)
[ ] Documentation update
[ ] Bug fix (template)
[ ] New rule (template)
[ ] Changes an existing rule (template)
[ ] Add autofix to a rule
[ ] Add a CLI option
[ ] Add something to the core
[x] Other, please explain:
Improve performance
What changes did you make? (Give an overview)
Changed token store's
utils.search
function logic to use binary search. This reduces the time complexity of the function from linear to logarithmic time, and results in a performance gain for sufficiently large arrays of tokens.References #16962
Measuring performance
To measure the performance improvement, I considered the time spent by
jsdoc/*
rules while runningnpm run lint
as suggested in #16962 (comment). On a zsh terminal, I run the commandand used a script to calculate the sum of the time and the total relative time usage from the generated text file.
Another useful measure I discovered is the time spent by Node.js in evaluating
BackwardTokenCommentCursor
, which can be captured using the rudimentary built-in profiler.BackwardTokenCommentCursor
makes heavy use of theutils.search
function, so I used this value to validate (double-check) the results I was getting with the previous command.My script:
And an example output:
Preliminary work
I run the test on three variations of the function
utils.search
: the original code withfindIndex
, my binary search implementation and the same binary search with an additional optimization that removes a conditional jump in 50% of the iterations on average, which Wikipedia calls alternative procedure.The code and the respective timing details are reported below.
It seems that the binary search does indeed reduce the time spent by
jsdoc/*
rules, although by a small amount. On the other hand, the extra optimization (alternative procedure) does not seem to improve performance measurably.With
findIndex
JSDoc rule stats
With binary search
Code
JSDoc rule stats
With binary search (alternative procedure)
Code
JSDoc rule stats
Update
After the first performance tests showed positive results I did a couple more changes to the original code. The first change consist in replacing a call to
Math.trunc()
with a bitwise-or operation (| 0
) to round a value to an integer. This bitwise operation was part of an earlier implementation (still found in the first commit), but later we switched toMath.trunc()
for clarity. Unfortunately, this method showed a negative impact on performance (as did otherMath
functions), so in the end we decided to switch back to a bitwise operation and to add a comment to clarify the intent.Another change I did was inlining the only call to the function
getSourceLocation
. This function was previously required as a callback forArray#findIndex()
, and earlier for LodashsortedIndexBy()
, but now that we control the search implementation, having this separate function seems unnecessary, as it only contains minimal logic, is not reused and does not help to clarify the code. It could also reduce performance because of the overhead of the additional invocation, although the effect would be negligible and difficult to quantify in practice.My tests suggest that the new binary search reduces the linting time for JSDoc rules of 6~11%, and the overall execution time for
npm run lint
is reduced by up to 3%.This improvement is consistent with a reduced time spent by
BackwardTokenCommentCursor
as captured by the Node.js profiler (lower is better):Before the change:
After the change:
Is there anything you'd like reviewers to focus on?
I'd be interested to know if others are getting similar timing stats.