-
Notifications
You must be signed in to change notification settings - Fork 30.1k
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
errors: improve error creation performance #46648
Conversation
Review requested:
|
846a306
to
15854a8
Compare
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.
Amazing!
cc @nodejs/performance |
I checked how much potential is left for further improvements and it turns out this is almost an ideal state. Our main overhead is due to creating subclasses. ~50% over the most simple errors without subclass. The other ~15-20% overhead are related to our error message creation. It would actually be possible to roughly cut the latter costs into half by creating a formatPreparation method that pre calculates states and caches these for the later usage. It would however be a non-trivial code part and I believe this is already pretty nice. |
@BridgeAR any chance to further explicitly optimise |
@ronag the current implementation for AbortErrors is pretty much the bare minimum of a regular error. The question is what are these used for? If their stack trace is important, we can't improve them further. If not, then there's room for improvement by hiding the frames (which are ~75% of all CPU cycles for a regular error). |
I'm not going to have time to review the entire PR this week but the changes in |
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, this is truly amazing
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.
RSLGTM. Awesome work!
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.
RSLGTM, this is absolutely incredible!
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.
🙌
general nit: there are several large numbers in messages for humans, but they're inconsistently delimited. for example 'The value of "port" is out of range. It must be >= 0 && <= 65535. Received 99_999'
. Why not delimit all human-friendly numbers?
This simplifies the implementation of the OUT_OF_RANGE error by reusing the current inspect functionality. Signed-off-by: Ruben Bridgewater <ruben@bridgewater.de>
The former implementation over allocated stack frames during error creation to substitute for internal frames that should be hidden to the user. This could have caused a significant performance overhead in deeply nested code. As side effect also simplify the setting of stackTraceLimits and improve the error related benchmarks. Signed-off-by: Ruben Bridgewater <ruben@bridgewater.de>
This makes sure all error instances use `new` to call the constructor. Signed-off-by: Ruben Bridgewater <ruben@bridgewater.de>
The improvement comes from the following changes: 1. Less properties are defined on the error instances, especially no Symbol. 2. The check if something is a NodeError or not is not needed anymore. 3. The setup of NodeError now makes sure that all heavy lifting is done up front instead of doing that during instance creation. To align with the WPT tests, the NodeErrors must have their constructor set to their base class. This was not the case for SystemErrors and is now aligned. Signed-off-by: Ruben Bridgewater <ruben@bridgewater.de>
This improves the error recreation performance due to limiting the stack trace that is generated and it makes sure NodeErrors only set properties inside of the actual error method. Signed-off-by: Ruben Bridgewater <ruben@bridgewater.de>
This fixes the issue that multiple prepareStackTrace functions existed that had to duplicate code in multiple spots. Instead, the diverging part is now replaced during runtime. This reduces the code overhead and makes sure there is no duplication. It also fixes the issue that `overrideStackTrace` usage would not adhere to the `hideStackFrame` calls. Frames are now removed before passing them to the override method. A second fix is for the repl: the stack frames passed to a user defined Error.prepareStackTrace() method are now in the correct order. As drive-by it also improves the performance for repl errors marginally. Signed-off-by: Ruben Bridgewater <ruben@bridgewater.de>
Signed-off-by: Ruben Bridgewater <ruben@bridgewater.de>
The function calls are more expensive than using the error printf format style. Signed-off-by: Ruben Bridgewater <ruben@bridgewater.de>
c6ec54f
to
40ab9c3
Compare
try { | ||
Error.stackTraceLimit = limit; | ||
} catch { | ||
stackTraceLimitWritable = -1; |
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.
Nit: A comment here on why this would throw would be helpful for future generations ;-)
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.
I suggest to address that in a follow-up PR? :)
// Setting the property in strict mode would cause this to throw an error
// while using the --frozen-intrinsics flag due to the writable attribute
// being set to false.
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.
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.
To fill the contract of --frozen-intrinsics
(opt-out of all possible prototype mutation), we need the property to be non-configurable, it's true that it could still be writable without introducing too much risk I believe. (but the flag name would be kinda lying, not the end of the world of course, but a bit harder to document in simple terms)
But even if we did that, we would probably still want the try catch in case some user code has changed this to non-writable for some reason, unless it's introducing too much maintenance burden. wdyt?
@BridgeAR It seems like there are some conflicts on the PR. Would you like to rebase it? |
I would actually say, that we should split this PR in multiple PRs so that it can be easier reviewed. |
This issue/PR was marked as stalled, it will be automatically closed in 30 days. If it should remain open, please leave a comment explaining why it should remain open. |
Closing this because it has stalled. Feel free to reopen if this issue/PR is still relevant, or to ping the collaborator who labelled it stalled if you have any questions. |
This is a first step to significantly faster NodeErorrs. The code itself should also be cleaned up in a following PR but it already became bigger as I worked upon different parts related to errors in Node.js.
This does change stack traces for users in few cases: originally we added extra stack frames to compensate for stack frames we want to hide. The mechanism as it was implemented had a high cost on all Node.js errors in case they were created in a deeply nested function. Stack frames that are removed are now not going to be compensated for anymore. I am going to think about other ways to do this later on again.
Refs: nodejs/performance#40