Skip to content

Commit

Permalink
perf(jest-runtime): load chalk only once per worker (#10864)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeysal authored Feb 11, 2021
1 parent 5ba0cc9 commit 42f78d4
Show file tree
Hide file tree
Showing 12 changed files with 93 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
/website/translated_docs
/website/i18n/*

/benchmarks/*/node_modules/

/reports/*

coverage
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@

### Performance

- `[jest-runtime]` Load `chalk` only once per worker ([#10864](https://github.com/facebook/jest/pull/10864))

## 26.6.3

### Fixes
Expand Down
1 change: 1 addition & 0 deletions benchmarks/test-file-overhead/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[1-9]*.test.js
8 changes: 8 additions & 0 deletions benchmarks/test-file-overhead/0.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

it('is fast', () => {});
7 changes: 7 additions & 0 deletions benchmarks/test-file-overhead/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
First, run `./prepare.sh` to generate the benchmark files. On Windows, use a Bash (WSL, Git, Cygwin …) to do this, but you can use CMD for the actual benchmark run if the CMD environment is what you want to benchmark for.

To run the benchmark, use a benchmarking tool such as [hyperfine](https://github.com/sharkdp/hyperfine):

```sh
hyperfine -w 3 -m 10 ../../jest /tmp/other-jest-clone-to-compare-against/jest
```
5 changes: 5 additions & 0 deletions benchmarks/test-file-overhead/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"jest": {
"testEnvironment": "node"
}
}
4 changes: 4 additions & 0 deletions benchmarks/test-file-overhead/prepare.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
for i in {1..499}; do
cp 0.test.js $i.test.js
done
11 changes: 11 additions & 0 deletions benchmarks/test-file-overhead/yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!

__metadata:
version: 4

"root-workspace-0b6124@workspace:.":
version: 0.0.0-use.local
resolution: "root-workspace-0b6124@workspace:."
languageName: unknown
linkType: soft
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ describe('Runtime', () => {
runtime.requireModule(modulePath);
}).toThrow(new Error('preprocessor must not run.'));
});

it('loads internal modules without applying transforms', async () => {
const runtime = await createRuntime(__filename, {
transform: {'\\.js$': './test_preprocessor'},
Expand All @@ -56,7 +55,6 @@ describe('Runtime', () => {
const exports = runtime.requireModule(modulePath);
expect(exports).toEqual({foo: 'foo'});
});

it('loads internal JSON modules without applying transforms', async () => {
const runtime = await createRuntime(__filename, {
transform: {'\\.json$': './test_json_preprocessor'},
Expand All @@ -68,5 +66,29 @@ describe('Runtime', () => {
const exports = runtime.requireInternalModule(modulePath);
expect(exports).toEqual({foo: 'bar'});
});

const OPTIMIZED_MODULE_EXAMPLE = 'chalk';
it('loads modules normally even if on the optimization list', () =>
createRuntime(__filename).then(runtime => {
const modulePath = path.resolve(
path.dirname(runtime.__mockRootPath),
'require-by-name.js',
);
const requireByName = runtime.requireModule(modulePath);
expect(requireByName(OPTIMIZED_MODULE_EXAMPLE)).not.toBe(
require(OPTIMIZED_MODULE_EXAMPLE),
);
}));
it('loads internal modules from outside if on the optimization list', () =>
createRuntime(__filename).then(runtime => {
const modulePath = path.resolve(
path.dirname(runtime.__mockRootPath),
'require-by-name.js',
);
const requireByName = runtime.requireInternalModule(modulePath);
expect(requireByName(OPTIMIZED_MODULE_EXAMPLE)).toBe(
require(OPTIMIZED_MODULE_EXAMPLE),
);
}));
});
});
10 changes: 10 additions & 0 deletions packages/jest-runtime/src/__tests__/test_root/require-by-name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

module.exports = moduleName => require(moduleName);
17 changes: 17 additions & 0 deletions packages/jest-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ const defaultTransformOptions: InternalModuleOptions = {
type InitialModule = Omit<Module, 'require' | 'parent' | 'paths'>;
type ModuleRegistry = Map<string, InitialModule | Module>;

// These are modules that we know
// * are safe to require from the outside (not stateful, not prone to errors passing in instances from different realms), and
// * take sufficiently long to require to warrant an optimization.
// When required from the outside, they use the worker's require cache and are thus
// only loaded once per worker, not once per test file.
// Use /benchmarks/test-file-overhead to measure the impact.
// Note that this only applies when they are required in an internal context;
// users who require one of these modules in their tests will still get the module from inside the VM.
// Prefer listing a module here only if it is impractical to use the jest-resolve-outside-vm-option where it is required,
// e.g. because there are many require sites spread across the dependency graph.
const INTERNAL_MODULE_REQUIRE_OUTSIDE_OPTIMIZED_MODULES = new Set(['chalk']);
const JEST_RESOLVE_OUTSIDE_VM_OPTION = Symbol.for(
'jest-resolve-outside-vm-option',
);
Expand Down Expand Up @@ -660,6 +671,12 @@ export default class Runtime {

requireInternalModule<T = unknown>(from: Config.Path, to?: string): T {
if (to) {
const require = (
nativeModule.createRequire ?? nativeModule.createRequireFromPath
)(from);
if (INTERNAL_MODULE_REQUIRE_OUTSIDE_OPTIMIZED_MODULES.has(to)) {
return require(to);
}
const outsideJestVmPath = decodePossibleOutsideJestVmPath(to);
if (outsideJestVmPath) {
return require(outsideJestVmPath);
Expand Down
4 changes: 2 additions & 2 deletions scripts/checkCopyrightHeaders.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ const GENERIC_IGNORED_PATTERNS = [
const CUSTOM_IGNORED_PATTERNS = [
'\\.(example|map)$',
'^examples/.*',
'^flow-typed/.*',
'^packages/expect/src/jasmineUtils\\.ts$',
'^packages/jest-config/src/vendor/jsonlint\\.js$',
'^packages/jest-diff/src/cleanupSemantic\\.ts$',
'^packages/jest-haste-map/src/watchers/common\\.js$',
'^packages/jest-haste-map/src/watchers/NodeWatcher\\.js$',
'^packages/jest-haste-map/src/watchers/RecrawlWarning\\.js$',
'^website/static/css/code-block-buttons\\.css$',
'^website/static/js/code-block-buttons\\.js',
'^website/static/js/code-block-buttons\\.js$',
'^benchmarks/test-file-overhead/prepare\\.sh$',
].map(createRegExp);

const IGNORED_PATTERNS = [
Expand Down

0 comments on commit 42f78d4

Please sign in to comment.