-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
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
Unify worker module map transmission w/ small perf benefit. #8237
Unify worker module map transmission w/ small perf benefit. #8237
Conversation
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.
🎉
Linter says that we can actually remove even more :D |
@@ -36,6 +35,30 @@ export default class ModuleMap { | |||
private readonly _raw: RawModuleMap; | |||
private json: SerializableModuleMap | undefined; | |||
|
|||
private static mapToArrayRecursive( |
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.
You're probably wondering: what is this?
Turns out the ModuleMap
wasn't being serialized in watch mode correctly but tests didn't catch it because we had 2 code paths.
1 code path = bug caught and fixed.
@@ -65,43 +65,6 @@ test('injects the serializable module map into each worker in watch mode', () => | |||
}); | |||
}); | |||
|
|||
test('does not inject the serializable module map in serial mode', () => { |
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.
pretty sure this code path no longer exists with my changes, please sanity check my assumption
Codecov Report
@@ Coverage Diff @@
## master #8237 +/- ##
==========================================
- Coverage 62.33% 62.28% -0.05%
==========================================
Files 265 265
Lines 10553 10556 +3
Branches 2565 2563 -2
==========================================
- Hits 6578 6575 -3
- Misses 3387 3393 +6
Partials 588 588
Continue to review full report at Codecov.
|
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.
Less code and better perf is an awesome outcome! 😀
@cpojer Would prefer you review before merge. |
The reason that we save the map to a file and read it from the worker is that I've found that on large module maps it used to take an insane amount of time to share it through workers. Doing it with two different implementations seemed like a good trade off. Before merging this, can you confirm this is actually faster on www and that it keeps scaling? |
Benchmarked against 11 test files (all of which are just a single test with a single assertion) on WWW. Ran old vs. new 10 times each to get a decent profile. This benchmarks 1.5% faster on average, which means that just this part is actually significantly faster since it's just a small part of the overall picture. 🥇 Other thoughts:
|
Oh wow, that's cool. Well, if you are confident, ship it. |
@cpojer I just ran it 2 more times to be sure. It's definitely a statistically significant improvement in terms of performance. Merging! |
private static mapFromArrayRecursive( | ||
arr: ReadonlyArray<[string, unknown]>, | ||
): Map<string, unknown> { | ||
if (arr[0] && Array.isArray(arr[1])) { |
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.
Why do you check for arr[1]
? Is there some constraint that there are always at least 2 items in arr when going recursive?
Checking for arr[0][1]
would make more sense.
The changes in @scotthovestadt you wouldn't by any chance be able to take a look? |
FWIW, my local diff (partial revert of this PR) which fixes the error (but based on the rest of the discussion here, introduces a perf regression) diff --git i/packages/jest-runner/src/index.ts w/packages/jest-runner/src/index.ts
index b58d881416..0f1a0a9846 100644
--- i/packages/jest-runner/src/index.ts
+++ w/packages/jest-runner/src/index.ts
@@ -155,12 +155,14 @@ export default class TestRunner {
onFailure?: OnTestFailure,
) {
const resolvers: Map<string, SerializableResolver> = new Map();
- for (const test of tests) {
- if (!resolvers.has(test.context.config.name)) {
- resolvers.set(test.context.config.name, {
- config: test.context.config,
- serializableModuleMap: test.context.moduleMap.toJSON(),
- });
+ if (watcher.isWatchMode()) {
+ for (const test of tests) {
+ if (!resolvers.has(test.context.config.name)) {
+ resolvers.set(test.context.config.name, {
+ config: test.context.config,
+ serializableModuleMap: test.context.moduleMap.toJSON(),
+ });
+ }
}
}
diff --git i/packages/jest-runner/src/testWorker.ts w/packages/jest-runner/src/testWorker.ts
index 919eeccd9c..091fe52094 100644
--- i/packages/jest-runner/src/testWorker.ts
+++ w/packages/jest-runner/src/testWorker.ts
@@ -58,24 +58,32 @@ const formatError = (error: string | ErrorWithCode): SerializableError => {
};
const resolvers = new Map<string, Resolver>();
-const getResolver = (config: Config.ProjectConfig) => {
- const resolver = resolvers.get(config.name);
- if (!resolver) {
- throw new Error('Cannot find resolver for: ' + config.name);
+const getResolver = (config: Config.ProjectConfig, moduleMap?: ModuleMap) => {
+ const name = config.name;
+ if (moduleMap || !resolvers.has(name)) {
+ resolvers.set(
+ name,
+ Runtime.createResolver(
+ config,
+ moduleMap || Runtime.createHasteMap(config).readModuleMap(),
+ ),
+ );
}
- return resolver;
+ return resolvers.get(name)!;
};
export function setup(setupData: {
serializableResolvers: Array<SerializableResolver>;
}): void {
- // Module maps that will be needed for the test runs are passed.
+ // Setup data is only used in watch mode to pass the latest version of all
+ // module maps that will be used during the test runs. Otherwise, module maps
+ // are loaded from disk as needed.
for (const {
config,
serializableModuleMap,
} of setupData.serializableResolvers) {
const moduleMap = ModuleMap.fromJSON(serializableModuleMap);
- resolvers.set(config.name, Runtime.createResolver(config, moduleMap));
+ getResolver(config, moduleMap);
}
} |
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. |
Summary
This PR unifies the way module maps are passed to the worker. Previously, we did it one way for watch mode and a different way for non-watch mode because our watch mode way was a lot slower.
I fixed that slowness for watch mode and realized while doing some performance and memory profiling that the watch mode way is now actually faster on a few levels:
Here's a benchmark of running
yarn jest packages/expect
, meant to profile starting up some workers and running a couple tests. Each profile was run 10 times after 3 warm ups.master
Time (mean ± σ): 3.902 s ± 0.120 s [User: 21.570 s, System: 5.105 s]
Range (min … max): 3.682 s … 4.084 s 10 run
this branch
Time (mean ± σ): 3.522 s ± 0.175 s [User: 19.722 s, System: 4.777 s]
Range (min … max): 3.356 s … 3.897 s 10 runs
It's faster. It's less code with a unified code path. It opens up more optimizations in the future.
Test plan