GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — lib: use Object.create(null) directly by TimothyGu · Pull Request #11930 · nodejs/node

Conversation

@TimothyGu

@TimothyGu TimothyGu commented Mar 19, 2017 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Since v8/v8@532c16e (included in v5.6), using Object.create(null) directly is now faster than using a constructor.

Refs: emberjs/ember.js#15001
Refs: https://crrev.com/532c16eca071df3ec8eed394dcebb932ef584ee6

/cc @nodejs/v8

querystring
                                                                 improvement confidence      p.value
 querystring/querystring-parse.js n=100000 type="altspaces"           5.40 %          * 1.400406e-02
 querystring/querystring-parse.js n=100000 type="encodefake"          5.70 %            6.377693e-02
 querystring/querystring-parse.js n=100000 type="encodelast"          3.49 %            1.214386e-01
 querystring/querystring-parse.js n=100000 type="encodemany"          5.08 %          * 1.427018e-02
 querystring/querystring-parse.js n=100000 type="manyblankpairs"     -7.21 %         ** 1.152391e-03
 querystring/querystring-parse.js n=100000 type="manypairs"           8.53 %        *** 6.779383e-10
 querystring/querystring-parse.js n=100000 type="multicharsep"        6.11 %         ** 8.321237e-03
 querystring/querystring-parse.js n=100000 type="multivalue"         12.44 %         ** 1.668042e-03
 querystring/querystring-parse.js n=100000 type="multivaluemany"     19.43 %        *** 1.919249e-09
 querystring/querystring-parse.js n=100000 type="noencode"            6.23 %        *** 2.348627e-04
map-bench
es/map-bench.js millions=3 method="object": 0.39794620430879774
es/map-bench.js millions=3 method="nullProtoObject": 0.41138972726767054
es/map-bench.js millions=3 method="nullProtoLiteralObject": 0.37403618701313923
es/map-bench.js millions=3 method="storageObject": 0.39906914551802625
es/map-bench.js millions=3 method="fakeMap": 0.3576056060343219
es/map-bench.js millions=3 method="map": 0.5858516350899148
Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • commit message follows commit guidelines
Affected core subsystem(s)

lib

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@TimothyGu TimothyGu added the performance Issues and PRs related to the performance of Node.js. label Mar 19, 2017
@nodejs-github-bot nodejs-github-bot added dont-land-on-v4.x lib / src Issues and PRs involving general changes in the lib/ or src/ directories. whatwg-url Issues and PRs related to the WHATWG URL implementation. labels Mar 19, 2017
@addaleax addaleax added the dont-land-on-v7.x label Mar 19, 2017
addaleax
addaleax approved these changes Mar 19, 2017
View reviewed changes
cjihrig
cjihrig approved these changes Mar 20, 2017
View reviewed changes

@cjihrig cjihrig left a comment

Copy link
Copy Markdown
Contributor

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.

Code LGTM. I worry that this will make debugging more difficult though.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@joyeecheung

joyeecheung commented Mar 20, 2017

Copy link
Copy Markdown
Member

There seems to be some regressions in the event benchmarks...

See numbers
                                                     improvement confidence      p.value
 events/ee-add-remove.js n=250000                       -43.29 %        *** 4.520847e-30
 events/ee-emit-multi-args.js n=2000000                   1.57 %            2.325176e-01
 events/ee-emit.js n=2000000                              8.37 %         ** 6.855389e-03
 events/ee-listener-count-on-prototype.js n=50000000    -96.06 %        *** 8.635026e-26
 events/ee-listeners-many.js n=5000000                  -17.51 %        *** 3.849835e-27
 events/ee-listeners.js n=5000000                       -56.36 %        *** 6.473337e-30
 events/ee-once.js n=20000000                           -51.46 %        *** 4.847882e-33
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@TimothyGu

TimothyGu commented Mar 20, 2017

Copy link
Copy Markdown
Member Author

Interesting.

The V8 optimization is mainly designed to accommodate map-like usage of objects, by automatically using so-called "slow" properties based on a hashmap instead of trying to find hidden classes. This makes it work especially well with cases like HTTP headers, FS cache, and query string parsing that don't have a fixed pattern of object properties.

On the other hand, an instance of EventEmitter often only has a few event names, and in case of the benchmarks they all only have one event 'dummy', so slow properties get (unfairly, arguably) penalized.

Take the benchmark with the worst regression, ee-listener-count-on-prototype.js. If we make the emitter have listeners for two events instead of just one, this PR actually drastically improves its performance:

                                                     improvement confidence      p.value
 events/ee-listener-count-on-prototype.js n=50000000     61.35 %        *** 2.676747e-11

I believe having two or more events is a considerably more common case than just having one event, and therefore I think this change is still justified.

benchmark diff
diff --git a/benchmark/events/ee-listener-count-on-prototype.js b/benchmark/events/ee-listener-count-on-prototype.js
index dfe7e27..1ef0f09 100644
--- a/benchmark/events/ee-listener-count-on-prototype.js
+++ b/benchmark/events/ee-listener-count-on-prototype.js
@@ -9,12 +9,12 @@ function main(conf) {
 
   var ee = new EventEmitter();
 
-  for (var k = 0; k < 10; k += 1)
-    ee.on('dummy', function() {});
+  for (var k = 0; k < 2 * 10; k += 1)
+    ee.on(`dummy${k % 2}`, function() {});
 
   bench.start();
   for (var i = 0; i < n; i += 1) {
-    ee.listenerCount('dummy');
+    ee.listenerCount(`dummy${i % 2}`);
   }
   bench.end(n);
 }
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@TimothyGu

TimothyGu commented Mar 20, 2017

Copy link
Copy Markdown
Member Author

@cjihrig

I worry that this will make debugging more difficult though.

Can you elaborate? I doubt having a generic name like StorageObject instead of nothing would improve debugging experience, and EventHandlers though descriptive is only used for a property named _events, which announces the intention of the object well enough.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@cjihrig

cjihrig commented Mar 20, 2017

Copy link
Copy Markdown
Contributor

I meant specifically in heap snapshots.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@joyeecheung

joyeecheung commented Mar 20, 2017 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

I think the benchmarks can add a few more configurations for different use cases.

Note that the diff in #11930 (comment) also removes the loop invariant(not sure if V8 could perform LICM on that previously).

Also +1 with @cjihrig, with Object.create(null) those objects probably won't be grouped together in heap snapshots and only have an id in their labels.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mscdex

mscdex commented Mar 20, 2017

Copy link
Copy Markdown
Contributor

What's up with the 'manyblankpairs' regression?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@TimothyGu

TimothyGu commented Mar 20, 2017

Copy link
Copy Markdown
Member Author

@mscdex, it can be attributed to the initial cost of object creation:

$ ./node benchmark/es/map-bench.js method=storageObjectCreation millions=4
es/map-bench.js millions=4 method="storageObjectCreation": 216.31376787921167
$ ./node benchmark/es/map-bench.js method=objectCreation millions=4
es/map-bench.js millions=4 method="objectCreation": 99.61981839655775
$ ./node benchmark/es/map-bench.js method=nullProtoObjectCreation millions=4
es/map-bench.js millions=4 method="nullProtoObjectCreation": 33.876842956365344

$ ./node --turbo benchmark/es/map-bench.js method=storageObjectCreation millions=4
es/map-bench.js millions=4 method="storageObjectCreation": 934.5947246800764
$ ./node --turbo benchmark/es/map-bench.js method=objectCreation millions=4
es/map-bench.js millions=4 method="objectCreation": 892.728215367111
$ ./node --turbo benchmark/es/map-bench.js method=nullProtoObjectCreation millions=4
es/map-bench.js millions=4 method="nullProtoObjectCreation": 28.154238858915456
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@joyeecheung

joyeecheung commented Mar 20, 2017

Copy link
Copy Markdown
Member

Before this PR (StorageObject have names and can be searched):

screen shot 2017-03-20 at 12 22 56 pm

After:

screen shot 2017-03-20 at 12 22 25 pm

I think the performance gain is probably worth it but wonder if @nodejs/v8 have any idea to work around this sacrafice

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mscdex

mscdex commented Mar 20, 2017

Copy link
Copy Markdown
Contributor

Is there any perf difference with StorageObject.prototype = null; ? I'm not sure why I didn't originally use that, but it seems to work similarly?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

lpinca
lpinca approved these changes Mar 20, 2017
View reviewed changes
@lpinca

lpinca commented Mar 20, 2017

Copy link
Copy Markdown
Member

@mscdex

$ node
> function StorageObject() {}
undefined
> StorageObject.prototype = null
null
> var o = new StorageObject()
undefined
> o.
o.__defineGetter__      o.__defineSetter__      o.__lookupGetter__      o.__lookupSetter__      o.__proto__
o.constructor           o.hasOwnProperty        o.isPrototypeOf         o.propertyIsEnumerable  o.toLocaleString
o.toString              o.valueOf
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

jasnell
jasnell approved these changes Mar 22, 2017
View reviewed changes

@jasnell jasnell left a comment

Copy link
Copy Markdown
Member

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.

changes LGTM but needs a rebase

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
TimothyGu added 3 commits March 22, 2017 11:30
@TimothyGu
benchmark: add more options to map-bench
6fd9a99
@TimothyGu
url: use a class for url[context]
db2f056
The object is used as a structure, not as a map, which `StorageObject`
was designed for.
@TimothyGu
lib: use Object.create(null) directly
9549e2d
After V8 5.6, using Object.create(null) directly is now faster than
using a constructor.

Refs: emberjs/ember.js#15001
Refs: https://crrev.com/532c16eca071df3ec8eed394dcebb932ef584ee6
@TimothyGu
TimothyGu force-pushed the storage-object branch from 6d1a545 to 9549e2d Compare March 23, 2017 02:01
@TimothyGu

TimothyGu commented Mar 23, 2017

Copy link
Copy Markdown
Member Author

@joyeecheung I added a commit that uses a dedicated class for url[context], which should make heap debugging for this case as easy as before. It also restores the performance loss caused by a switch to Object.create(null), since the context always has a fixed set of properties.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@TimothyGu

TimothyGu commented Mar 24, 2017

Copy link
Copy Markdown
Member Author

Landed in d9b0e4c...cfc8422.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@TimothyGu TimothyGu closed this Mar 24, 2017
@TimothyGu
TimothyGu deleted the storage-object branch March 24, 2017 22:26
TimothyGu added a commit to TimothyGu/node that referenced this pull request Mar 24, 2017
@TimothyGu
benchmark: add more options to map-bench
a6e69f8
PR-URL: nodejs#11930
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Colin Ihrig <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Reviewed-By: James M Snell <[email protected]>
TimothyGu added a commit to TimothyGu/node that referenced this pull request Mar 24, 2017
@TimothyGu
url: use a class for WHATWG url[context]
14a9195
The object is used as a structure, not as a map, which `StorageObject`
was designed for.

PR-URL: nodejs#11930
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Colin Ihrig <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Reviewed-By: James M Snell <[email protected]>
TimothyGu added a commit to TimothyGu/node that referenced this pull request Mar 24, 2017
@TimothyGu
lib: use Object.create(null) directly
cfc8422
After V8 5.6, using Object.create(null) directly is now faster than
using a constructor for map-like objects.

PR-URL: nodejs#11930
Refs: emberjs/ember.js#15001
Refs: https://crrev.com/532c16eca071df3ec8eed394dcebb932ef584ee6
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Colin Ihrig <[email protected]>
Reviewed-By: Luigi Pinca <[email protected]>
Reviewed-By: James M Snell <[email protected]>
@mscdex

mscdex commented Mar 25, 2017 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

@TimothyGu I'm seeing contradicting results on master with some more simple, direct benchmarks:

'use strict';

var n = 1e7;
var input = { mode: 0o666, flags: 'r', start: 5, end: 100 };

function StorageObject() {}
StorageObject.prototype = Object.create(null);
function copyObject(source) {
  const target = new StorageObject();
  for (var key in source)
    target[key] = source[key];
  return target;
}

function copyObject2(source) {
  const target = Object.create(null);
  for (var key in source)
    target[key] = source[key];
  return target;
}

var r;
console.time('copyObject');
for (var i = 0; i < n; ++i) {
  r = copyObject(input);
  r.mode >>>= 0;
}
console.timeEnd('copyObject');

console.time('copyObject2');
for (var i = 0; i < n; ++i) {
  r = copyObject2(input);
  r.mode >>>= 0;
}
console.timeEnd('copyObject2');

Results:

copyObject: 2487.810ms
copyObject2: 8500.589ms

With just the first two properties in input:

copyObject: 1600.427ms
copyObject2: 3985.290ms

With just the first property in input:

copyObject: 838.353ms
copyObject2: 2215.232ms
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mscdex

mscdex commented Mar 25, 2017

Copy link
Copy Markdown
Contributor

I should add that I just tried with V8 5.7 which was just merged into master and the results are similar.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lpinca lpinca mentioned this pull request Mar 25, 2017
High memory usage for upgraded http requests #11868
Closed
@mscdex

mscdex commented Mar 26, 2017 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Ah, I just noticed that es/map-bench.js isn't actually benchmarking object creation, but instead property access. There is a much larger difference with object creation in StorageObject's favor compared to property access where StorageObject is still pretty close to the other top performing alternatives.

I think we should revert the changes in this PR (except maybe the URL changes where the properties are known beforehand?). /cc @nodejs/collaborators

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lpinca

lpinca commented Mar 26, 2017 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

I also noticed the huge difference with object creation time but this also "fixes" something weird which happens when using instances of

function StorageObject() {}
StorageObject.prototype = Object.create(null);

I've described the behavior in #11868 (comment). @mscdex can you take a look?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lpinca lpinca mentioned this pull request Mar 27, 2017
Drop EmptyObject and use Object.create(null) again emberjs/ember.js#15001
Closed
@mscdex mscdex mentioned this pull request Mar 28, 2017
fs: Optimizations 3: The Revenge #12105
Closed
3 tasks done
@jasnell jasnell mentioned this pull request Apr 4, 2017
8.0.0 Release Proposal #12220
Closed
This was referenced Apr 19, 2017
(v7.x backport) url: updates to the WHATWG URL parser #12507
Merged
events: remove code duplication #12655
Closed
events: performance regression in event benchmarks #12657
Closed
@TimothyGu TimothyGu mentioned this pull request Apr 29, 2017
console: add console.count() and console.clear() #12678
Closed
4 tasks done
@daiyam daiyam mentioned this pull request Aug 12, 2017
Object vs Dictionary kaoscript/kaoscript#35
Closed
@twoi

twoi commented Apr 1, 2019

Copy link
Copy Markdown

This is a breaking change. Maybe it's faster now, but it is not backwards compatible, e.g. it broke the following code:

var reqArgs = url.parse(req.url, true).query;
if (reqArgs.hasOwnProperty("id"))
  ...

now throws TypeError: reqArgs.hasOwnProperty is not a function

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mcollina

mcollina commented Apr 1, 2019

Copy link
Copy Markdown
Member

@twoi I imagine you upgraded from a release to another, can you give us the version numbers? Was this a patch/minor release or a major release? Thanks.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@twoi

twoi commented Apr 1, 2019

Copy link
Copy Markdown

We upgraded from 0.8.15 to 11.9 - finally, because we are using a native module that (pre-napi) needed to ported / rebuilt on every Node.js version upgrade, which include all the vast v8 refactorings.

But this change apparently made it into Node.js version 8.0.0. It's filed under Semver-patch commits, even though I'd argue it's a breaking change.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@BridgeAR

BridgeAR commented Apr 1, 2019

Copy link
Copy Markdown
Member

@twoi this PR is semver-patch. The removal of the prototype was done in #6092 which is marked as semver-major (v6). Is there anything actionable you ask for?

I highly recommend to never use object.hasOwnProperty('foobar'). Instead just use:
Object.prototoype.hasOwnProperty.call(object, 'foobar'). That is a significantly safer way to do the same.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@twoi

twoi commented Apr 2, 2019 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

@BridgeAR Thanks for the advice. Agree.

I don't have anything actionable, just wanted to make aware of the fact that this innocent looking change can (and in fact did) break someone's code.

Which, it seems, people noticed during testing: https://github.com/nodejs/node/pull/6092/files#r58880619
And then turned it semver-major.

I am adding more testing myself, so this won't catch me unawares anymore... Case closed.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@BridgeAR

BridgeAR commented Apr 2, 2019

Copy link
Copy Markdown
Member

@twoi

this innocent looking change can (and in fact did) break someone's code

The breaking change was done in #6092, not here. This PR is just some code refactoring.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@twoi

twoi commented Apr 2, 2019

Copy link
Copy Markdown

@BridgeAR I see. Makes sense. Thanks

BridgeAR reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lpinca lpinca mentioned this pull request Aug 30, 2021
events: speed up emit by create faster null prototype object #39939
Closed
@mscdex mscdex mentioned this pull request Sep 14, 2022
events: improve performance by ~120% #44627
Closed
@lpinca lpinca mentioned this pull request Oct 27, 2023
events: improve events performance #50422
Closed
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@jasnell jasnell jasnell approved these changes
@addaleax addaleax addaleax approved these changes
@lpinca lpinca lpinca approved these changes
@cjihrig cjihrig cjihrig approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

lib / src Issues and PRs involving general changes in the lib/ or src/ directories. performance Issues and PRs related to the performance of Node.js. whatwg-url Issues and PRs related to the WHATWG URL implementation.

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

11 participants

@TimothyGu @joyeecheung @cjihrig @mscdex @lpinca @twoi @mcollina @BridgeAR @jasnell @addaleax @nodejs-github-bot
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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.