Skip to content

util: improve inspect performance - #14881

Closed
BridgeAR wants to merge 6 commits into
nodejs:masterfrom
BridgeAR:improve-inspect
Closed

util: improve inspect performance#14881
BridgeAR wants to merge 6 commits into
nodejs:masterfrom
BridgeAR:improve-inspect

Conversation

@BridgeAR

@BridgeARBridgeAR commented Aug 17, 2017

Copy link
Copy Markdown
Member

I reworked a couple of things in util inspect for performance. The code is fully covered.

EDIT: Updated the comment here and benchmarks.

 util/format.js type="no-replace" n=2000000 1676.61 % *** 6.038361e-60
util/inspect-array.js type="denseArray_showHidden" len=100000 n=1000 87.89 % *** 3.315801e-79
util/inspect-array.js type="denseArray" len=100000 n=1000 68.83 % *** 5.648717e-52
util/inspect-array.js type="mixedArray" len=100000 n=1000 21.22 % *** 2.438066e-52
util/inspect-array.js type="sparseArray" len=100000 n=1000 6.87 % *** 9.551672e-50
util/inspect-proxy.js n=1000000 418.34 % *** 5.193464e-71
util/inspect.js option="colors" method="Array" n=2000000 121.92 % *** 1.136217e-39
util/inspect.js option="colors" method="Date" n=2000000 294.42 % *** 3.899375e-18
util/inspect.js option="colors" method="Error" n=2000000 507.31 % *** 6.416333e-18
util/inspect.js option="colors" method="Object_deep_ln" n=2000000 257.20 % *** 8.804036e-23
util/inspect.js option="colors" method="Object_empty" n=2000000 704.27 % *** 8.422940e-46
util/inspect.js option="colors" method="Object" n=2000000 372.04 % *** 3.430467e-19
util/inspect.js option="colors" method="Set" n=2000000 443.65 % *** 7.503642e-30
util/inspect.js option="colors" method="String_boxed" n=2000000 458.19 % *** 4.199961e-23
util/inspect.js option="colors" method="String_complex" n=2000000 875.46 % *** 4.377511e-42
util/inspect.js option="colors" method="String" n=5000000 2209.03 % *** 3.336942e-23
util/inspect.js option="colors" method="TypedArray_extra" n=2000000 269.77 % *** 6.353860e-22
util/inspect.js option="colors" method="TypedArray" n=2000000 244.82 % *** 1.157433e-47
util/inspect.js option="none" method="Array" n=2000000 298.39 % *** 6.163207e-35
util/inspect.js option="none" method="Date" n=2000000 308.41 % *** 3.828139e-26
util/inspect.js option="none" method="Error" n=2000000 499.46 % *** 2.519987e-17
util/inspect.js option="none" method="Object_deep_ln" n=2000000 470.60 % *** 7.069515e-27
util/inspect.js option="none" method="Object_empty" n=2000000 703.54 % *** 5.821877e-41
util/inspect.js option="none" method="Object" n=2000000 678.48 % *** 1.752143e-21
util/inspect.js option="none" method="Set" n=2000000 739.62 % *** 5.402939e-18
util/inspect.js option="none" method="String_boxed" n=2000000 483.94 % *** 2.756513e-37
util/inspect.js option="none" method="String_complex" n=2000000 1082.41 % *** 3.900304e-64
util/inspect.js option="none" method="String" n=5000000 3954.65 % *** 1.558337e-26
util/inspect.js option="none" method="TypedArray_extra" n=2000000 410.59 % *** 1.364578e-19
util/inspect.js option="none" method="TypedArray" n=2000000 387.47 % *** 1.789663e-23
util/inspect.js option="showHidden" method="Array" n=2000000 272.92 % *** 2.332172e-29
util/inspect.js option="showHidden" method="Date" n=2000000 307.75 % *** 4.581806e-16
util/inspect.js option="showHidden" method="Error" n=2000000 254.46 % *** 1.552997e-19
util/inspect.js option="showHidden" method="Object_deep_ln" n=2000000 422.07 % *** 4.184839e-46
util/inspect.js option="showHidden" method="Object_empty" n=2000000 665.61 % *** 2.095402e-24
util/inspect.js option="showHidden" method="Object" n=2000000 626.02 % *** 6.570478e-39
util/inspect.js option="showHidden" method="Set" n=2000000 815.83 % *** 3.634105e-32
util/inspect.js option="showHidden" method="String_boxed" n=2000000 680.82 % *** 3.104074e-27
util/inspect.js option="showHidden" method="String_complex" n=2000000 1058.45 % *** 5.543800e-49
util/inspect.js option="showHidden" method="String" n=5000000 3473.09 % *** 6.496121e-29
util/inspect.js option="showHidden" method="TypedArray_extra" n=2000000 345.93 % *** 9.418444e-36
util/inspect.js option="showHidden" method="TypedArray" n=2000000 316.96 % *** 4.742310e-35

The main optimizations are

  • Removed visibleKeys
  • Removed proxy cache
  • Removed Object.assign
  • No key concatenating anymore
  • No key recalculating anymore
  • Improved indentation logic
  • Improved string escape logic
  • Added many fast paths
  • Optimized code branches a lot
  • Optimized (boxed) primitive handling
  • Inline code if possible
  • Only check extra keys if necessary

Besides that there were lots of smaller optimizations, the
code got a bit cleaned up and a few more tests got in.

Also two bug fixes came.

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)

util, benchmark

@BridgeARBridgeAR added benchmark Issues and PRs related to the benchmark subsystem. blocked PRs that are blocked by other issues or PRs. performance Issues and PRs related to the performance of Node.js. util Issues and PRs related to the built-in util module. labels Aug 17, 2017
@BridgeARBridgeAR mentioned this pull request Aug 17, 2017
4 tasks
Comment threadlib/util.js Outdated

@andrasqandrasqAug 17, 2017

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a long series of if - else if - else if statements can also be formatted as a switch (true) { ... } (note that switch does a strict comparison, ie matches not truthy but true). Strictly a layout issue, otherwise completely equivalent, both O(n) in the number of tests.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I you prefer that I can change it, otherwise I would just keep it as is.

@addaleaxaddaleax left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great work, thank you!

Comment threadlib/util.js Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grammar nit: lazy → lazily

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment threadlib/util.js Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ctx.seen = [...ctx.seenSet]?

Also, I, personally, would be fine with having seen just be a getter/setter pair.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment threadlib/util.js Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this could just be turned into a Set as well

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that and it was actually a bit slower in my benchmarks. When 6.1 lands I might have another look at it.

Comment threadlib/util.js Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This really sounds like it would return a boolean ;)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment threadlib/util.js Outdated
Comment threadlib/util.js Outdated
Comment threadlib/util.js Outdated
@addaleax

Copy link
Copy Markdown
Member

Also, sorry for the merge conflict, I fixed Evan’s nit while landing my PR.

@BridgeAR

Copy link
Copy Markdown
MemberAuthor

@addaleax I addressed your comments and don't worry about the conflicts, I was aware of those changes and I'll rebase after #14880 lands :)
Those are minor changes anyway.

Comment threadlib/util.js Outdated

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reverted my change to a Set here: the seen part will only contain up to ctx.depth + 1 elements and in the regular case that is three. Using a Set for such a low amount of elements seems unnecessary, as it increases the code complexity due to having to handle both, an array and a set in case customInspect is set.

@BridgeAR

BridgeAR commented Aug 21, 2017

Copy link
Copy Markdown
MemberAuthor

I rebased and added a new array fast path. So PTAL

Just for fun - this is how the profiling looks like with Array(1e5).fill(1)

 [Summary]:
ticks total nonlib name
261 0.6% 0.6% JavaScript
39502 94.0% 94.3% C++
1797 4.3% 4.3% GC
129 0.3% Shared libraries
2147 5.1% Unaccounted
[C++ entry points]:
ticks cpp total name
37497 99.2% 89.2% v8::internal::Runtime_ObjectKeys(int, v8::internal::Object**, v8::internal::Isolate*)
75 0.2% 0.2% v8::internal::Builtin_ObjectGetOwnPropertyDescriptor(int, v8::internal::Object**, v8::internal::Isolate*)

0.6 % JavaScript 🤣

@BridgeAR

Copy link
Copy Markdown
MemberAuthor

@refackrefack left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 nit otherwise LGTM

Comment threadlib/util.js Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not worth it.

  1. changes util.inspect.length
  2. drives my IDE crazy

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I do not have a strong opinion.
I went ahead and changed it back to a similar way as before to make you happy ;-)

@BridgeAR

Copy link
Copy Markdown
MemberAuthor

So I pushed a bit more code and updated the benchmarks. The result is pretty decent now.

@BridgeAR

Copy link
Copy Markdown
MemberAuthor

Ok, the new benchmarks are outdated again as I was able to improve some more things that had significant impact on showHidden. So just think of most showHidden results +100%.

@BridgeARBridgeAR added the wip Issues and PRs that are still a work in progress. label Aug 23, 2017
@BridgeARBridgeAR removed blocked PRs that are blocked by other issues or PRs. wip Issues and PRs that are still a work in progress. labels Aug 24, 2017
@BridgeAR

Copy link
Copy Markdown
MemberAuthor

So after looking at it again I added some final changes. The benchmarks are updated in the main part. As the code still changed significantly, please take a close look again. @addaleax@refack@andrasq

And I have an issue with one test running this locally but I can not explain why the test is failing. I get the following output. @bnoordhuis you wrote that test, would you be so kind and have a look at it? Because I do not see the connection of the error to my changes...

	async-hooks doctool inspector known_issues message parallel pseudo-tty sequential \
addons addons-napi
[02:05|% 91|+ 1642|- 0]: release stack_overflow_async length differs. expect=4
actual=2
patterns:
pattern = ^RangeError\:\ Maximum\ call\ stack\ size\ exceeded$
pattern = ^\ \ \ \ at\ f\ \(.*test.*message.*stack\_overflow\_async\.js\:.*\)$
pattern = ^\ \ \ \ at\ f\ \(.*test.*message.*stack\_overflow\_async\.js\:7\:.*\)$
pattern = ^\ \ \ \ at\ f\ \(.*test.*message.*stack\_overflow\_async\.js\:7\:.*\)$
outlines:
outline = (node:21361) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): RangeError: Maximum call stack size exceeded
outline = (node:21361) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
=== release stack_overflow_async === Path: message/stack_overflow_async
(node:21361) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): RangeError: Maximum call stack size exceeded
(node:21361) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Command: out/Release/node --stack_trace_limit=3 /home/ruben/repos/node/node/test/message/stack_overflow_async.js
[02:46|% 100|+ 1785|- 1]: Done 

@addaleax

Copy link
Copy Markdown
Member

@BridgeAR That’s a really new test, and I’m pretty sure it’s not related to your PR

@BridgeAR

Copy link
Copy Markdown
MemberAuthor

@BridgeAR

BridgeAR commented Aug 24, 2017

Copy link
Copy Markdown
MemberAuthor

@addaleax the weird part is that is it happening only with this PR. On master it does not fail. It is reproducible for me on my local machine and also the CI fails exactly at this test. [EDIT] And the test passes if I call console.log() on top of the function call. Only calling console.log fails. [/EDIT]

@BridgeAR

Copy link
Copy Markdown
MemberAuthor

@bnoordhuis it would be nice if you could have a look at this PR because it somehow breaks this test even though there should not be any connection. The only way for me to fix the test is by calling process.stdout.write("") (or console.log()) somewhere before the test starts.

@BridgeARBridgeAR mentioned this pull request Aug 31, 2017
4 tasks
@BridgeAR

Copy link
Copy Markdown
MemberAuthor

@nodejs/collaborators PTAL

@mcollinamcollina left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with both CI and CITGM green. Those type of large scale perf improvements can lead to broken production code.

This should be semver-minor at least, and it should bake for LTS for some month before being backported.

addaleax pushed a commit to addaleax/ayo that referenced this pull request Sep 17, 2017
In addition use the newer common.expectsError version.
PR-URL: nodejs/node#14881
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
@jasnell

Copy link
Copy Markdown
Member

This needs a backport pr to land in v8.x

jasnell pushed a commit that referenced this pull request Sep 20, 2017
PR-URL: #14881
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
jasnell pushed a commit that referenced this pull request Sep 20, 2017
In addition use the newer common.expectsError version.
PR-URL: #14881
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
This was referenced Sep 20, 2017
BridgeAR added a commit to BridgeAR/node that referenced this pull request Sep 20, 2017
PR-URL: nodejs#14881
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
BridgeAR added a commit to BridgeAR/node that referenced this pull request Sep 20, 2017
The main optimizations are
- Removed visibleKeys
- Removed proxy cache
- Removed Object.assign
- No key concatenating anymore
- No key recalculating anymore
- Improved indentation logic
- Improved string escape logic
- Added many fast paths
- Optimized code branches a lot
- Optimized (boxed) primitive handling
- Inline code if possible
- Only check extra keys if necessary
- Guard against unnecessary more expensive calls
This also fixes a bug with special array number keys as e.g. "00".
Besides that there were lots of smaller optimizations, the
code got a bit cleaned up and a few more tests got in.
PR-URL: nodejs#14881Fixes: nodejs#15288
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
BridgeAR added a commit to BridgeAR/node that referenced this pull request Sep 20, 2017
This fixes a issue brought up in nodejs#15288.
PR-URL: nodejs#14881
Refs: nodejs#15288
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
BridgeAR added a commit to BridgeAR/node that referenced this pull request Sep 20, 2017
Remove unnecessary code parts and outdated code
PR-URL: nodejs#14881
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
jasnell pushed a commit that referenced this pull request Sep 21, 2017
PR-URL: #14881
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
jasnell pushed a commit that referenced this pull request Sep 21, 2017
The main optimizations are
- Removed visibleKeys
- Removed proxy cache
- Removed Object.assign
- No key concatenating anymore
- No key recalculating anymore
- Improved indentation logic
- Improved string escape logic
- Added many fast paths
- Optimized code branches a lot
- Optimized (boxed) primitive handling
- Inline code if possible
- Only check extra keys if necessary
- Guard against unnecessary more expensive calls
This also fixes a bug with special array number keys as e.g. "00".
Besides that there were lots of smaller optimizations, the
code got a bit cleaned up and a few more tests got in.
PR-URL: #14881Fixes: #15288
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
jasnell pushed a commit that referenced this pull request Sep 21, 2017
This fixes a issue brought up in #15288.
PR-URL: #14881
Refs: #15288
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
jasnell pushed a commit that referenced this pull request Sep 21, 2017
Remove unnecessary code parts and outdated code
PR-URL: #14881
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Qard pushed a commit to Qard/ayo that referenced this pull request Sep 21, 2017
PR-URL: nodejs/node#14881
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Qard pushed a commit to Qard/ayo that referenced this pull request Sep 21, 2017
PR-URL: nodejs/node#14881
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Qard pushed a commit to Qard/ayo that referenced this pull request Sep 21, 2017
The main optimizations are
- Removed visibleKeys
- Removed proxy cache
- Removed Object.assign
- No key concatenating anymore
- No key recalculating anymore
- Improved indentation logic
- Improved string escape logic
- Added many fast paths
- Optimized code branches a lot
- Optimized (boxed) primitive handling
- Inline code if possible
- Only check extra keys if necessary
- Guard against unnecessary more expensive calls
This also fixes a bug with special array number keys as e.g. "00".
Besides that there were lots of smaller optimizations, the
code got a bit cleaned up and a few more tests got in.
PR-URL: nodejs/node#14881Fixes: nodejs/node#15288
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Qard pushed a commit to Qard/ayo that referenced this pull request Sep 21, 2017
This fixes a issue brought up in #15288.
PR-URL: nodejs/node#14881
Refs: nodejs/node#15288
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Qard pushed a commit to Qard/ayo that referenced this pull request Sep 21, 2017
Remove unnecessary code parts and outdated code
PR-URL: nodejs/node#14881
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Qard pushed a commit to Qard/ayo that referenced this pull request Sep 21, 2017
In addition use the newer common.expectsError version.
PR-URL: nodejs/node#14881
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
@MylesBorinsMylesBorins added baking-for-lts PRs that need to wait before landing in a LTS release. lts-watch-v6.x labels Oct 17, 2017
@MylesBorins

Copy link
Copy Markdown
Contributor

should this land in LTS? If so it will need to bake a bit longer.

Please change labels as appropriate

@MylesBorinsMylesBorins removed the baking-for-lts PRs that need to wait before landing in a LTS release. label Aug 17, 2018
@BridgeAR
BridgeAR deleted the improve-inspect branch April 1, 2019 23:37
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

benchmarkIssues and PRs related to the benchmark subsystem.performanceIssues and PRs related to the performance of Node.js.utilIssues and PRs related to the built-in util module.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

13 participants

@BridgeAR@addaleax@mscdex@TimothyGu@bmeurer@jasnell@MylesBorins@mcollina@refack@ronkorving@benjamingr@tniessen@andrasq