Skip to content
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

benchmark: add microbenchmarks for ES Map #7581

Closed
wants to merge 3 commits into from

Conversation

rvagg
Copy link
Member

@rvagg rvagg commented Jul 7, 2016

Given the discussion in #6102 about Map still being too slow to use I figured I'd see with a benchmark. @mscdex, @jasnell please review and let me know if you think this is valid.

es/map-bench.js method="pojo" millions="10": 1.07989
es/map-bench.js method="fakeMap" millions="10": 0.85223
es/map-bench.js method="map" millions="10": 1.77412

As for why the fakeMap test is faster ... I got nothing ...


Update after @fhinkel noticed a serious flaw:

v6

es/map-bench.js millions=1 method="object": 0.18984850416666324
es/map-bench.js millions=1 method="nullProtoObject": 0.16044478686078825
es/map-bench.js millions=1 method="fakeMap": 0.1311882248350222
es/map-bench.js millions=1 method="map": 0.3121014937928409

master

es/map-bench.js millions=1 method="object": 0.2220851555085364
es/map-bench.js millions=1 method="nullProtoObject": 0.19131381289232038
es/map-bench.js millions=1 method="fakeMap": 0.17931948397493905
es/map-bench.js millions=1 method="map": 0.28127750318241235

Times are ops/sec, so higher is better

@nodejs-github-bot nodejs-github-bot added the benchmark Issues and PRs related to the benchmark subsystem. label Jul 7, 2016
const assert = require('assert');

const bench = common.createBenchmark(main, {
method: ['pojo', 'fakeMap', 'map'],
Copy link
Contributor

Choose a reason for hiding this comment

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

pojo?

Copy link
Member Author

Choose a reason for hiding this comment

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

plain old javascript object ... comes from plain old java object, maybe that's just for us old java dogs

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll just call it 'object' since it's not obvious

@addaleax
Copy link
Member

addaleax commented Jul 7, 2016

Not sure if it matters, but it might be nice to have one for m = Object.create(null), too.

@rvagg
Copy link
Member Author

rvagg commented Jul 7, 2016

ok, s/pojo/object and added one for a null-prototype object

es/map-bench.js method="object" millions="10": 1.07722
es/map-bench.js method="nullProtoObject" millions="10": 1.04962
es/map-bench.js method="fakeMap" millions="10": 0.88726
es/map-bench.js method="map" millions="10": 1.78555

@cjihrig
Copy link
Contributor

cjihrig commented Jul 7, 2016

Should this utilize common.v8ForceOptimization()?


function runObject(n) {
const m = {};
let i = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we're still preferring var over let.

@rvagg
Copy link
Member Author

rvagg commented Jul 7, 2016

s/let/var, thanks for pointing that out.

I don't know if this is a candidate for common.v8ForceOptimization() or not, there's a bit of state to pass around, it'd at least be passing in m to optimized functions from the parent bench functions, would that make a difference?

@cjihrig
Copy link
Contributor

cjihrig commented Jul 7, 2016

I'll defer to someone with more experience working on the benchmarks, but es/restparams-bench.js, for example, forces optimization with arguments.

@mscdex mscdex added the v8 engine Issues and PRs related to the V8 dependency. label Jul 7, 2016
@mscdex
Copy link
Contributor

mscdex commented Jul 7, 2016

AFAIK that should be (million) ops/sec, so technically Map would be the fastest (largest value). That's quite surprising.

@jasnell
Copy link
Member

jasnell commented Jul 7, 2016

Interesting! Map is definitely showing to be faster... even if you take creation of the Map into consideration on each loop.

Unmodified version:

~/node/main/node [check] $ ./node benchmark/es/map-bench.js
es/map-bench.js method="object" millions="10": 0.84279
es/map-bench.js method="nullProtoObject" millions="10": 0.84733
es/map-bench.js method="fakeMap" millions="10": 0.69652
es/map-bench.js method="map" millions="10": 1.71819

Modified to include map creation on each loop:

~/node/main/node [check] $ ./node benchmark/es/map-bench.js
es/map-bench.js method="object" millions="10": 0.77879
es/map-bench.js method="nullProtoObject" millions="10": 0.77240
es/map-bench.js method="fakeMap" millions="10": 0.37927
es/map-bench.js method="map" millions="10": 1.61652

@rvagg
Copy link
Member Author

rvagg commented Jul 8, 2016

ok then, that is interesting and explains why fakeMap looked "better" than the pojo, perhaps we go ahead and migrate headers to a Map eh?

@mscdex
Copy link
Contributor

mscdex commented Jul 8, 2016

@rvagg That would probably have to happen at least in v7 or later since that would be a pretty big change.

@simonkcleung
Copy link

The number should be the higher the better?

@mscdex
Copy link
Contributor

mscdex commented Jul 10, 2016

@simonkcleung Yes. Values are always operations per second.

@jasnell
Copy link
Member

jasnell commented Jul 20, 2016

Moving the querystring and headers objects to Maps definitely makes sense given the perf results but @Fishrock123 is right about targeting such a change for v7. We can likely go ahead and get that change landed in master now as a semver-major.

var i = 0;
bench.start();
for (; i < n; i++) {
m['i' + n] = n;
Copy link
Member

Choose a reason for hiding this comment

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

Are we trying to set the same two properties ('i10000000' and 's10000000') 10 million times? If so, maybe use keys like 'a', 'b'. Or should the keys be 'i' + i and 's' + i?

@fhinkel
Copy link
Member

fhinkel commented Jul 29, 2016

@rvagg Thanks for putting this together.

When we're using named keys o[key] on an object, access is usually megamorphic. V8 only has limited space in the megamorphic IC stub cache, around 2500 entries. The same cache is used for all megamorphic ICs, which can be completely unrelated to o.

Every time we add a property to an object, we create a new transition map. Only if we add properties in exactly the same order can we reuse the maps. This is often not the case, for example when we parse request headers into _headers[key]. Because we need to keep track of the transitions, the number of maps quickly increases. All these maps fight for entries in the megamorphic IC stub cache, eventually overwriting maps for other objects that are unrelated to o, which slows down the application overall.

So +1 for moving to maps!

@jasnell
Copy link
Member

jasnell commented Jul 29, 2016

btw, LGTM for getting this landed. :-)

@jasnell
Copy link
Member

jasnell commented Aug 4, 2016

@rvagg ... is this ready to land?

@addaleax
Copy link
Member

addaleax commented Aug 4, 2016

I think @fhinkel’s comment should still be addressed?

@addaleax
Copy link
Member

ping @rvagg

@rvagg
Copy link
Member Author

rvagg commented Oct 18, 2016

Thanks for catching that @fhinkel, that changes everything!

Updates pushed, pls review folks.

v6

es/map-bench.js thousands=100 method="object": 0.26339184977485885
es/map-bench.js thousands=100 method="nullProtoObject": 0.23501547831916525
es/map-bench.js thousands=100 method="fakeMap": 0.212913507413461
es/map-bench.js thousands=100 method="map": 0.7449254838002358

master

es/map-bench.js thousands=100 method="object": 0.35728933045594424
es/map-bench.js thousands=100 method="nullProtoObject": 0.029615543283847124
es/map-bench.js thousands=100 method="fakeMap": 0.028427891784159526
es/map-bench.js thousands=100 method="map": 0.15582199860352638

@fhinkel
Copy link
Member

fhinkel commented Oct 18, 2016

LGTM. Should we update the first comment with the new times and also point out, that it's ops/sec rather than seconds (so higher is faster).

@rvagg
Copy link
Member Author

rvagg commented Oct 18, 2016

Good idea, done! @fhinkel is there a known speed regression for maps in later V8? We're comparing 5.1 to 5.4 here and that's a big slowdown, or could the test not be getting at the real numbers?

@fhinkel
Copy link
Member

fhinkel commented Oct 18, 2016

I wonder if something else was going on during the benchmarking, because nullProtoObject and fakeMap also have a 10x slowdown. Let me try on my machine ...

@rvagg
Copy link
Member Author

rvagg commented Oct 18, 2016

yea, I think you're right, new run on master:

es/map-bench.js thousands=100 method="object": 0.10768888702936505
es/map-bench.js thousands=100 method="nullProtoObject": 0.11350308225508328
es/map-bench.js thousands=100 method="fakeMap": 0.12039689515526422
es/map-bench.js thousands=100 method="map": 0.38445207528844944

@rvagg
Copy link
Member Author

rvagg commented Oct 18, 2016

Too much variability between runs, bumping it back up to the millions and it stabilises a bit more:

v6

es/map-bench.js millions=1 method="object": 0.18984850416666324
es/map-bench.js millions=1 method="nullProtoObject": 0.16044478686078825
es/map-bench.js millions=1 method="fakeMap": 0.1311882248350222
es/map-bench.js millions=1 method="map": 0.3121014937928409

master

es/map-bench.js millions=1 method="object": 0.2220851555085364
es/map-bench.js millions=1 method="nullProtoObject": 0.19131381289232038
es/map-bench.js millions=1 method="fakeMap": 0.17931948397493905
es/map-bench.js millions=1 method="map": 0.28127750318241235

@fhinkel
Copy link
Member

fhinkel commented Oct 18, 2016

I think we can blame the variation on timing issues. I don't get a regression going from v6.0.0 to master.

Node v6.0.0:

~/node/benchmark/es$ node --version
v6.0.0
~/node/benchmark/es$ node -e "console.log(process.versions.v8)"
5.0.71.35
~/node/benchmark/es$ node map-bench.js 
es/map-bench.js millions=1 method="object": 0.3697918357000556
es/map-bench.js millions=1 method="nullProtoObject": 0.41595417356334285
es/map-bench.js millions=1 method="fakeMap": 0.36001723720769746
es/map-bench.js millions=1 method="map":  0.5557669893876821

Node v6.7.0:

~/node/benchmark/es$ node -e "console.log(process.versions.v8)"
5.1.281.83
~/node/benchmark/es$ node map-bench.js 
es/map-bench.js millions=1 method="object": 0.4192028932525165
es/map-bench.js millions=1 method="nullProtoObject": 0.43625871109888
es/map-bench.js millions=1 method="fakeMap": 0.3631661863732719
es/map-bench.js millions=1 method="map": 0.5750817453541195

Node master:

~/node/benchmark/es$ ~/node/node -e "console.log(process.versions.v8)"
5.5.0 (candidate)
~/node/benchmark/es$ ~/node/node map-bench.js 
es/map-bench.js millions=1 method="object": 0.43575744375713865
es/map-bench.js millions=1 method="nullProtoObject": 0.4588149868110948
es/map-bench.js millions=1 method="fakeMap": 0.4051635672142672
es/map-bench.js millions=1 method="map": 0.6342556913214041

@rvagg rvagg force-pushed the master branch 2 times, most recently from c133999 to 83c7a88 Compare October 18, 2016 17:02
@rvagg rvagg closed this Oct 29, 2016
@rvagg rvagg deleted the es-map-bench branch October 29, 2016 07:05
@rvagg
Copy link
Member Author

rvagg commented Oct 29, 2016

landed @ 07cc9df

rvagg added a commit that referenced this pull request Oct 29, 2016
PR-URL: #7581
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Franziska Hinkelmann <ranziska.hinkelmann@gmail.com>
evanlucas pushed a commit that referenced this pull request Nov 3, 2016
PR-URL: #7581
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Franziska Hinkelmann <ranziska.hinkelmann@gmail.com>
MylesBorins pushed a commit that referenced this pull request Nov 22, 2016
PR-URL: #7581
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Franziska Hinkelmann <ranziska.hinkelmann@gmail.com>
MylesBorins pushed a commit that referenced this pull request Nov 22, 2016
PR-URL: #7581
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Franziska Hinkelmann <ranziska.hinkelmann@gmail.com>
This was referenced Nov 22, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
benchmark Issues and PRs related to the benchmark subsystem. v8 engine Issues and PRs related to the V8 dependency.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants