-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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 isAsyncFunction #11408
Conversation
@AbdelrahmanHafez |
With the improved benchmark from #11415 before:
after:
As you can see it improves the valid sync path significantly. |
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.
My only concern with this PR is the caching. We try to avoid caching because we don't want to unexpectedly cause extra memory usage. Granted this is less of a concern with WeakMap()
, but I'd prefer if we didn't do caching here.
Potential alternative approach to avoid the overhead of inspect()
:
const asyncFunctionProto = Object.getPrototypeOf(async function() {});
Object.getPrototypeOf(v) === asyncFunctionProto; // true for async functions, false for regular functions
avoid caching by checking based on async FunctionPrototype
@vkarpov15 Added unit tests. For my curiosity I checked what happens if the function returns a Promise, and it is of course false. inspect also just knows that it is a sync function. |
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! 👍
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.
Thanks 👍
I was playing around and realized that isAsyncFunction is using utils.inspect and startsWitht. So I tested the benchmark/validate and got
So I replaced startsWith with indexOf which is about 10 times faster than startsWith and benchmarked again:
No performance gain, despite we know that indexOf is about 10 x faster indicates that we have a different bottleneck, utils.inspect.
So I thought: Ok, there is a call to utils.inspect which is basically stringifying the function to just check if the string begins with
[AsyncFunction:
So I was like: We get non-primitive values for the isAsyncFunction-check. So I can use a WeakMap to cache the information if it is an async Function or not. And then I benchmarked again:
Nice gain.
To make sure, that dont get an Error when passing a primitive type, I added a check if we pass a primitive type and benched again:
To compare how close we are if we are basically skipping the check, as the benchmark is not using async functions, i patched isAsyncFunction to return false.
So with this PR we are avoiding the bottleneck very good.
So from 76k to 104k is not bad.. Also compared to #11298 where we started with 46k in mongoose 6.1.10 for the valid path, this means we basically doubled our performance for validate from mongoose 6.2.0.