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

HasteMap with native maps #6960

Merged
merged 5 commits into from
Sep 12, 2018

Conversation

rubennorte
Copy link
Contributor

@rubennorte rubennorte commented Sep 11, 2018

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
Copy link
Member

SimenB commented Sep 11, 2018

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

😲 That's awesome!

@SimenB
Copy link
Member

SimenB commented Sep 11, 2018

Can you update the changelog?

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

@rubennorte
Copy link
Contributor Author

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

@@ -132,6 +161,14 @@ export default class ModuleMap {
set,
);
}

static createEmpty() {
Copy link
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.

} from 'types/HasteMap';

import H from './constants';

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
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 […]"

const moduleMetadata = hasteMap.map[fileMetadata[H.ID]];
const fileMetadata = hasteMap.files.get(filePath);
if (!fileMetadata) {
// Should never happen
Copy link
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.

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
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
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
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.

Copy link
Member

@cpojer cpojer left a comment

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 :)

CHANGELOG.md Outdated
@@ -3,6 +3,7 @@
### 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
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

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
@@ -68,6 +68,7 @@ jest.mock('fs', () => {
});

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

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));

Copy link
Contributor Author

@rubennorte rubennorte Sep 13, 2018

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.

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
Copy link
Contributor

jdalton commented Sep 13, 2018

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

@rubennorte
Copy link
Contributor Author

rubennorte commented Sep 14, 2018

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

@mxmaxime
Copy link

mxmaxime commented Sep 30, 2018

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

"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

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