From 570f088798333906c99db297d6e80cbcfbdfb34c Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Sun, 22 Nov 2020 22:14:52 +0000 Subject: [PATCH 1/3] perf(jest-runtime): load `chalk` only once per worker --- CHANGELOG.md | 2 ++ .../__tests__/runtime_internal_module.test.js | 26 +++++++++++++++++-- .../__tests__/test_root/require-by-name.js | 10 +++++++ packages/jest-runtime/src/index.ts | 16 ++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 packages/jest-runtime/src/__tests__/test_root/require-by-name.js diff --git a/CHANGELOG.md b/CHANGELOG.md index ea43cca7622f..6dc8f3169adf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/packages/jest-runtime/src/__tests__/runtime_internal_module.test.js b/packages/jest-runtime/src/__tests__/runtime_internal_module.test.js index aeca406d3ea2..71293e89c33b 100644 --- a/packages/jest-runtime/src/__tests__/runtime_internal_module.test.js +++ b/packages/jest-runtime/src/__tests__/runtime_internal_module.test.js @@ -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'}, @@ -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'}, @@ -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), + ); + })); }); }); diff --git a/packages/jest-runtime/src/__tests__/test_root/require-by-name.js b/packages/jest-runtime/src/__tests__/test_root/require-by-name.js new file mode 100644 index 000000000000..793879bcfa07 --- /dev/null +++ b/packages/jest-runtime/src/__tests__/test_root/require-by-name.js @@ -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); diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index a4992f045f4f..81f941c78d56 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -95,6 +95,16 @@ const defaultTransformOptions: InternalModuleOptions = { type InitialModule = Omit; type ModuleRegistry = Map; +// 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. +// 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', ); @@ -660,6 +670,12 @@ export default class Runtime { requireInternalModule(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); From e18b9535808f7e03c13478b7d744fdb511e34071 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Tue, 9 Feb 2021 22:30:44 +0100 Subject: [PATCH 2/3] test(perf): add per test file overhead benchmark --- .gitignore | 2 ++ benchmarks/test-file-overhead/.gitignore | 1 + benchmarks/test-file-overhead/0.test.js | 1 + benchmarks/test-file-overhead/README.md | 7 +++++++ benchmarks/test-file-overhead/package.json | 5 +++++ benchmarks/test-file-overhead/prepare.sh | 4 ++++ benchmarks/test-file-overhead/yarn.lock | 11 +++++++++++ packages/jest-runtime/src/index.ts | 1 + 8 files changed, 32 insertions(+) create mode 100644 benchmarks/test-file-overhead/.gitignore create mode 100644 benchmarks/test-file-overhead/0.test.js create mode 100644 benchmarks/test-file-overhead/README.md create mode 100644 benchmarks/test-file-overhead/package.json create mode 100755 benchmarks/test-file-overhead/prepare.sh create mode 100644 benchmarks/test-file-overhead/yarn.lock diff --git a/.gitignore b/.gitignore index d513cf11ba99..4e7db024f4c0 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ /website/translated_docs /website/i18n/* +/benchmarks/*/node_modules/ + /reports/* coverage diff --git a/benchmarks/test-file-overhead/.gitignore b/benchmarks/test-file-overhead/.gitignore new file mode 100644 index 000000000000..a68ced87888c --- /dev/null +++ b/benchmarks/test-file-overhead/.gitignore @@ -0,0 +1 @@ +[1-9]*.test.js diff --git a/benchmarks/test-file-overhead/0.test.js b/benchmarks/test-file-overhead/0.test.js new file mode 100644 index 000000000000..2929750922ea --- /dev/null +++ b/benchmarks/test-file-overhead/0.test.js @@ -0,0 +1 @@ +it('is fast', () => {}); diff --git a/benchmarks/test-file-overhead/README.md b/benchmarks/test-file-overhead/README.md new file mode 100644 index 000000000000..d2be5fdc5d4f --- /dev/null +++ b/benchmarks/test-file-overhead/README.md @@ -0,0 +1,7 @@ +First, run `./prepare.sh` to generate the benchmark files. On Windows, use a Bash (WSL, Git, Cygwinm ...) 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 +``` diff --git a/benchmarks/test-file-overhead/package.json b/benchmarks/test-file-overhead/package.json new file mode 100644 index 000000000000..148788b25446 --- /dev/null +++ b/benchmarks/test-file-overhead/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "testEnvironment": "node" + } +} diff --git a/benchmarks/test-file-overhead/prepare.sh b/benchmarks/test-file-overhead/prepare.sh new file mode 100755 index 000000000000..569a785e1294 --- /dev/null +++ b/benchmarks/test-file-overhead/prepare.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +for i in {1..499}; do + cp 0.test.js $i.test.js +done diff --git a/benchmarks/test-file-overhead/yarn.lock b/benchmarks/test-file-overhead/yarn.lock new file mode 100644 index 000000000000..00246b971113 --- /dev/null +++ b/benchmarks/test-file-overhead/yarn.lock @@ -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 diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 81f941c78d56..2aa3f6739ec6 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -100,6 +100,7 @@ type ModuleRegistry = Map; // * 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, From 169c7dbdcfba9b4a0349eec9bdee2038e0e36167 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Thu, 11 Feb 2021 08:37:57 +0100 Subject: [PATCH 3/3] add copyright header --- benchmarks/test-file-overhead/0.test.js | 7 +++++++ benchmarks/test-file-overhead/README.md | 2 +- scripts/checkCopyrightHeaders.js | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/benchmarks/test-file-overhead/0.test.js b/benchmarks/test-file-overhead/0.test.js index 2929750922ea..bed12c324a11 100644 --- a/benchmarks/test-file-overhead/0.test.js +++ b/benchmarks/test-file-overhead/0.test.js @@ -1 +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', () => {}); diff --git a/benchmarks/test-file-overhead/README.md b/benchmarks/test-file-overhead/README.md index d2be5fdc5d4f..d60998eeab8f 100644 --- a/benchmarks/test-file-overhead/README.md +++ b/benchmarks/test-file-overhead/README.md @@ -1,4 +1,4 @@ -First, run `./prepare.sh` to generate the benchmark files. On Windows, use a Bash (WSL, Git, Cygwinm ...) to do this, but you can use CMD for the actual benchmark run if the CMD environment is what you want to benchmark for. +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): diff --git a/scripts/checkCopyrightHeaders.js b/scripts/checkCopyrightHeaders.js index 49cd0f2e913c..b67ee285d591 100755 --- a/scripts/checkCopyrightHeaders.js +++ b/scripts/checkCopyrightHeaders.js @@ -99,7 +99,6 @@ 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$', @@ -107,7 +106,8 @@ const CUSTOM_IGNORED_PATTERNS = [ '^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 = [