-
Notifications
You must be signed in to change notification settings - Fork 28
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: improve performance, reduce allocations, and avoid promises #102
Open
negezor
wants to merge
17
commits into
unjs:main
Choose a base branch
from
negezor:refactor-for-perf
base: main
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
…in callHook & callHookParallel
@pi0 can you review this? |
kurtextrem
reviewed
Jul 27, 2024
src/hookable.ts
Outdated
|
||
// Splice will ensure that all fns are called once, and free all | ||
// unreg functions from memory. | ||
removeFns = []; |
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.
removeFns.length = 0
is probably enough to trigger GC and avoids another alloc
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.
Done
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.
Hi there! I love micro-optimizations. During one of my profiling sessions of my application on low-end devices, I noticed a significant consumption of
hookable
on the flame graph. After reviewing the code, I identified some possible improvements, which I’ve included in this PR. I’ve added benchmarks to support my claims. Here are some of the improvements:unshift
, we can directly passname
tocallHookWith
, as all the arguments there are collected using the spread operator. This removes unnecessary allocations and improves performance.Promise
as much as possible because it is a performance killer. I replaced thePromise
chain with a middleware pattern inserialTaskCaller
.serialTaskCaller
andparallelTaskCaller
allow us to avoid unnecessary operations if there are no hooks. However, this breaks the test forcreateDebugger
as it returns the hook name in the arguments. I need consultation on this matter, as skipping the step significantly boosts performance. How should we proceed?delete
negatively impacts performance as it de-optimizes the object. A better option is to set the property toundefined
. The only downside is that if we print the_hooks
object, we’ll see something like{ "hello": undefined }
.Object.assign
indeprecateHooks
. Since we call thedeprecateHook
method anyway, nothing will be missed.addHooks
, we can set an empty array instead of callingsplice
in the release function each time. Plus, this approach uses fewer characters.removeAllHooks
, we can simply set an empty object and let the GC do the work. The only justification for the current behavior might be to keep a reference to the old object, but I don’t see any objective reasons for this.removeHook
, we can create an alias that slightly reduces the final bundle size and improves performance, as we don't need to compute the property.There are also potential performance improvements, such as:
callHookWith
, we could skip thecaller
call if there are no hooks to call, but this is a major change, and I’m unsure about it. This could eliminate the need for the third optimization.args.shift()
in utility functions.I hope the entire Vue ecosystem will benefit greatly from these changes. If I'm wrong about anything, please correct me 😅
Performance was tested on the following hardware:
CPU:
AMD 7950x3D
System:
Arch Linux on Windows 11, WSL 2
Node.js:
v22.4.0
Before
After