Skip to content

HasteMap with native maps#6960

Merged
rubennorte merged 5 commits into
jestjs:masterfrom
rubennorte:hastemap-with-native-maps
Sep 12, 2018
Merged

HasteMap with native maps#6960
rubennorte merged 5 commits into
jestjs:masterfrom
rubennorte:hastemap-with-native-maps

Conversation

@rubennorte

@rubennorte rubennorte commented Sep 11, 2018

Copy link
Copy Markdown
Contributor

Summary

This changes the data structure used to store the metadata for files, modules, mocks and duplicated modules in the Haste Map from Object (without prototypes) to Map.

The performance of Map is much better than Object to implement dictionaries when they contain a large amount of entries, which is usually what we have here. After testing this change in Facebook we've seen a ~20% reduction of the time to run all tests, which is a massive performance improvement for us.

This only modifies the internal data structures used by Jest and its end users will not be affected by it (other than performance). The change in the jest-haste-map package is breaking though, as it might affect other packages depending on it (including jest packages, which have been updated here).

Test plan

I've updated all tests related to this change (including a change to make the data structures more encapsulated for the rest of packages). I've also tested this in the Facebook infrastructure (including running all tests, running a subset of the tests, watch mode and coverage reporting).

@SimenB

SimenB commented Sep 11, 2018

Copy link
Copy Markdown
Member

After testing this change in Facebook we've seen a ~20% reduction of the time to run all tests

😲 That's awesome!

@SimenB

SimenB commented Sep 11, 2018

Copy link
Copy Markdown
Member

Can you update the changelog?

A question: is this change observable from the outside (aka a breaking change)?

@rubennorte

Copy link
Copy Markdown
Contributor Author

@SimenB sorry I was meant to talk about that in the description. I'll add it now.

);
}

static createEmpty() {

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.

just create. It's implied that if you create one without arguments it would be empty.

const EMPTY_MAP = {};

export opaque type SerializableModuleMap = {
// There isn't an easy way to extract the type of the entries of a Map

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.

"There is no easier way […]"

Comment thread packages/jest-haste-map/src/index.js Outdated
const moduleMetadata = hasteMap.map[fileMetadata[H.ID]];
const fileMetadata = hasteMap.files.get(filePath);
if (!fileMetadata) {
// Should never happen

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 comment is superfluous.

Comment thread packages/jest-haste-map/src/index.js Outdated
const fileMetadata = hasteMap.files.get(filePath);
if (!fileMetadata) {
// Should never happen
throw new Error('File to process was not found in the haste map');

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.

Prefix with jest-haste-map: , end with a .

files: copy(hasteMap.files),
map: copy(hasteMap.map),
mocks: copy(hasteMap.mocks),
clocks: new Map(hasteMap.clocks),

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 creates a shallow copy, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, like copy did.

@cpojer cpojer 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 super solid work @rubennorte. For historical context, when this code was initially written, v8.serialize did not exist and plain objects were much faster than Maps. I'm glad this change now not only improves startup time but also lookup time when resolving modules. Thanks for sending this PR :)

Comment thread CHANGELOG.md Outdated
### Features

- `[babel-jest]` Add support for `babel.config.js` added in Babel 7.0.0 ([#6911](https://github.com/facebook/jest/pull/6911))
- `[jest-haste-map]` Replaced internal data structures to improve performance ([#6960](https://github.com/facebook/jest/pull/6960))

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.

Include **breaking**?

@rubennorte rubennorte force-pushed the hastemap-with-native-maps branch from 742f16d to 342f76a Compare September 12, 2018 12:37
@codecov-io

Copy link
Copy Markdown

Codecov Report

Merging #6960 into master will decrease coverage by 0.08%.
The diff coverage is 89.28%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #6960      +/-   ##
==========================================
- Coverage   66.97%   66.89%   -0.09%     
==========================================
  Files         250      250              
  Lines       10381    10422      +41     
  Branches        4        3       -1     
==========================================
+ Hits         6953     6972      +19     
- Misses       3427     3449      +22     
  Partials        1        1
Impacted Files Coverage Δ
packages/jest-runner/src/index.js 47.82% <ø> (ø) ⬆️
packages/jest-runner/src/test_worker.js 0% <0%> (ø) ⬆️
packages/jest-haste-map/src/crawlers/node.js 98.78% <100%> (ø) ⬆️
packages/jest-haste-map/src/crawlers/watchman.js 94.8% <100%> (-0.07%) ⬇️
packages/jest-haste-map/src/module_map.js 88.23% <100%> (+0.73%) ⬆️
packages/jest-haste-map/src/haste_fs.js 57.14% <83.33%> (+5.14%) ⬆️
packages/jest-haste-map/src/index.js 90.02% <96%> (-5.7%) ⬇️
packages/jest-cli/src/lib/update_global_config.js 95.45% <0%> (-0.2%) ⬇️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 608b711...2ee6b7d. Read the comment docs.

@rubennorte rubennorte merged commit 9822231 into jestjs:master Sep 12, 2018
@rubennorte rubennorte deleted the hastemap-with-native-maps branch September 12, 2018 13:42
});

const pearMatcher = path => /pear/.test(path);
const createMap = obj => new Map(Object.keys(obj).map(key => [key, obj[key]]));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Random question from a stranger here:

Is Object.entries compiled / usable here?

const createMap = obj => new Map(Object.entries(obj));

@rubennorte rubennorte Sep 13, 2018

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I used it before but we don't include polyfills and we support Node 6 (which doesn't support it), so I had to remove it. Object.entries was first available in Node 7.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice! Thanks for the feedback.

@jdalton

jdalton commented Sep 13, 2018

Copy link
Copy Markdown
Contributor

@rubennorte In what versions of Node do you see perf wins in?

@rubennorte

rubennorte commented Sep 14, 2018

Copy link
Copy Markdown
Contributor Author

@jdalton we mainly tested this in Node v9.11.2, but we saw it's also better in v10.10.0.

@mxmaxime

mxmaxime commented Sep 30, 2018

Copy link
Copy Markdown

Very interesting.
I think it's related to the v8 hash tables optimization (https://v8.dev/blog/hash-code). If so, this perf boost is in V8 v6.3+

@nickshanks

Copy link
Copy Markdown

"The performance of Map is much better than Object to implement dictionaries when they contain a large amount of entries" — Are there any public data showing when Map should be used over Object?

@github-actions

Copy link
Copy Markdown

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators May 12, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants