-
-
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
Performance loss between jest 25.1.0 and 25.2.7 #9776
Comments
We switched from our own resolution algorithm to using A reproduction would help to debug this. |
@SLKnutson could you try to comment out the Just remove the try-catch. Another thing that would be great to try would be to cache the diff --git i/packages/jest-resolve/src/defaultResolver.ts w/packages/jest-resolve/src/defaultResolver.ts
index 4e990f8b6..0b68a7b42 100644
--- i/packages/jest-resolve/src/defaultResolver.ts
+++ w/packages/jest-resolve/src/defaultResolver.ts
@@ -43,23 +43,14 @@ export default function defaultResolver(
preserveSymlinks: false,
});
- try {
- // Dereference symlinks to ensure we don't create a separate
- // module instance depending on how it was referenced.
- const resolved = realpath(result);
-
- if (resolved) {
- return resolved;
- }
- } catch {
- // ignore
- }
-
- return result;
+ // Dereference symlinks to ensure we don't create a separate
+ // module instance depending on how it was referenced.
+ return realpathCached(result);
}
export function clearDefaultResolverCache(): void {
checkedPaths.clear();
+ checkedRealpathPaths.clear();
}
enum IPathType {
@@ -107,3 +98,29 @@ function isFile(file: Config.Path): boolean {
function isDirectory(dir: Config.Path): boolean {
return statSyncCached(dir) === IPathType.DIRECTORY;
}
+
+const checkedRealpathPaths = new Map<string, string>();
+function realpathCached(path: Config.Path): Config.Path {
+ let result = checkedRealpathPaths.get(path);
+
+ if (result !== undefined) {
+ return result;
+ }
+
+ try {
+ result = realpath(path);
+ } catch {
+ // ignore
+ }
+
+ if (!result) {
+ result = path;
+ }
+
+ checkedRealpathPaths.set(path, result);
+
+ // also cache the result in case it's ever referenced directly - no reason to `realpath` that as well
+ checkedRealpathPaths.set(result, result);
+
+ return result;
+} If you use diff --git a/node_modules/jest-resolve/build/defaultResolver.js b/node_modules/jest-resolve/build/defaultResolver.js
index 61d1341..20a4590 100644
--- a/node_modules/jest-resolve/build/defaultResolver.js
+++ b/node_modules/jest-resolve/build/defaultResolver.js
@@ -123,25 +123,15 @@ function defaultResolver(path, options) {
moduleDirectory: options.moduleDirectory,
paths: options.paths,
preserveSymlinks: false
- });
+ }); // Dereference symlinks to ensure we don't create a separate
+ // module instance depending on how it was referenced.
- try {
- // Dereference symlinks to ensure we don't create a separate
- // module instance depending on how it was referenced.
- const resolved = (0, _realpathNative().sync)(result);
-
- if (resolved) {
- return resolved;
- }
- } catch (_unused) {
- // ignore
- }
-
- return result;
+ return realpathCached(result);
}
function clearDefaultResolverCache() {
checkedPaths.clear();
+ checkedRealpathPaths.clear();
}
var IPathType;
@@ -195,3 +185,28 @@ function isFile(file) {
function isDirectory(dir) {
return statSyncCached(dir) === IPathType.DIRECTORY;
}
+
+const checkedRealpathPaths = new Map();
+
+function realpathCached(path) {
+ let result = checkedRealpathPaths.get(path);
+
+ if (result !== undefined) {
+ return result;
+ }
+
+ try {
+ result = (0, _realpathNative().sync)(path);
+ } catch (_unused) {
+ // ignore
+ }
+
+ if (!result) {
+ result = path;
+ }
+
+ checkedRealpathPaths.set(path, result); // also cache the result in case it's ever referenced directly - no reason to `realpath` that as well
+
+ checkedRealpathPaths.set(result, result);
+ return result;
+} |
Without realpath call: ~320 seconds I also tried without the realpath call and setting "preserveSymlinks=true" in the resolve call. With that, I got ~105 seconds. That disables the call to fs.realpathSync in sync.js. We could be running into this issue nodejs/node-v0.x-archive#7902 |
Cool, thanks for testing! Too bad it made no real difference 😅 What happens if we keep the Our https://nodejs.org/api/fs.html#fs_fs_realpathsync_native_path_options If perf is still horrible, I think we might need to roll it back and introduce some sort of (on my machine I see no difference between any of the discussed options, so it's hard for me to test this, unfortunately) |
With preserveSymlinks: true and the realpath call enabled it runs at ~120 seconds. That seems like a good easy win. |
Ah, wonderful! I think that's safe, wanna send a PR? @ljharb this might be interesting to you. Using |
@SimenB thanks for the ping; i'd generally try to avoid using |
@SLKnutson if you could test swapping out with |
Ah, I misunderstood; you're saying using the native one is better. In that case, I'd be fine conditionally invoking it in |
Right, I can see my comment was a bit ambiguous, sorry about that. I can open a PR, sure 👍 If nothing else it would be good to run |
@SimenB When running using I can create a PR if you would like, but I would prefer not to because I don't have jest forked and setup on my computer. |
@SLKnutson right, thanks! on closer thought I don't think it's correct in Jest - we want to resolve the symlink on every directory we lookup, not just the final path. Making the change I suggested leads to a failing unit test, which exposes the bug it introduces. Using native Thanks for testing this for us @SLKnutson, I'll ping again if and when it's ready to test again! |
@SLKnutson you should be able to install You might also be interested in #9732 which would allow us to effectively set |
Thanks @SimenB. I tried out the same code I was testing before with jest 25.2.7 installed and resolve 1.16.1 and saw runs of ~215 seconds. I also got similar results with jest 25.4.0 and resolve 1.16.1. Good work! |
That's great, thanks for testing! Still some ways of the 140 sec you saw with 25.1.0, though. Could you try updating to Built version: 'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
exports.default = defaultResolver;
exports.clearDefaultResolverCache = clearDefaultResolverCache;
function fs() {
const data = _interopRequireWildcard(require('fs'));
fs = function () {
return data;
};
return data;
}
function _resolve() {
const data = require('resolve');
_resolve = function () {
return data;
};
return data;
}
function _browserResolve() {
const data = require('browser-resolve');
_browserResolve = function () {
return data;
};
return data;
}
function _realpathNative() {
const data = require('realpath-native');
_realpathNative = function () {
return data;
};
return data;
}
function _jestPnpResolver() {
const data = _interopRequireDefault(require('jest-pnp-resolver'));
_jestPnpResolver = function () {
return data;
};
return data;
}
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {default: obj};
}
function _getRequireWildcardCache() {
if (typeof WeakMap !== 'function') return null;
var cache = new WeakMap();
_getRequireWildcardCache = function () {
return cache;
};
return cache;
}
function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
return obj;
}
if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
return {default: obj};
}
var cache = _getRequireWildcardCache();
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {};
var hasPropertyDescriptor =
Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = hasPropertyDescriptor
? Object.getOwnPropertyDescriptor(obj, key)
: null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
/**
* 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.
*/
function defaultResolver(path, options) {
// @ts-ignore: the "pnp" version named isn't in DefinitelyTyped
if (process.versions.pnp) {
return (0, _jestPnpResolver().default)(path, options);
}
const resolve = options.browser ? _browserResolve().sync : _resolve().sync;
const result = resolve(path, {
basedir: options.basedir,
extensions: options.extensions,
isDirectory,
isFile,
moduleDirectory: options.moduleDirectory,
paths: options.paths,
preserveSymlinks: false,
// @ts-ignore: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/44137
realpathSync
}); // Dereference symlinks to ensure we don't create a separate
// module instance depending on how it was referenced.
return realpathSync(result);
}
function clearDefaultResolverCache() {
checkedPaths.clear();
checkedRealpathPaths.clear();
}
var IPathType;
(function (IPathType) {
IPathType[(IPathType['FILE'] = 1)] = 'FILE';
IPathType[(IPathType['DIRECTORY'] = 2)] = 'DIRECTORY';
IPathType[(IPathType['OTHER'] = 3)] = 'OTHER';
})(IPathType || (IPathType = {}));
const checkedPaths = new Map();
function statSyncCached(path) {
const result = checkedPaths.get(path);
if (result !== undefined) {
return result;
}
let stat;
try {
stat = fs().statSync(path);
} catch (e) {
if (!(e && (e.code === 'ENOENT' || e.code === 'ENOTDIR'))) {
throw e;
}
}
if (stat) {
if (stat.isFile() || stat.isFIFO()) {
checkedPaths.set(path, IPathType.FILE);
return IPathType.FILE;
} else if (stat.isDirectory()) {
checkedPaths.set(path, IPathType.DIRECTORY);
return IPathType.DIRECTORY;
}
}
checkedPaths.set(path, IPathType.OTHER);
return IPathType.OTHER;
}
const checkedRealpathPaths = new Map();
function realpathCached(path) {
let result = checkedRealpathPaths.get(path);
if (result !== undefined) {
return result;
}
try {
result = (0, _realpathNative().sync)(path);
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}
if (!result) {
result = path;
}
checkedRealpathPaths.set(path, result);
if (path !== result) {
// also cache the result in case it's ever referenced directly - no reason to `realpath` that as well
checkedRealpathPaths.set(result, result);
}
return result;
}
/*
* helper functions
*/
function isFile(file) {
return statSyncCached(file) === IPathType.FILE;
}
function isDirectory(dir) {
return statSyncCached(dir) === IPathType.DIRECTORY;
}
function realpathSync(file) {
return realpathCached(file);
} |
With those changes, on the latest version of jest (25.4.0), I had test runs of about 115 seconds. That's the fastest I've seen our tests run in a long time. |
Ah, wonderful! Thanks so much for your help digging into this |
any plans for a new release? |
25.5 is out |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
💥 Regression Report
We run about 7000 tests total across 630 test files without coverage (see #9457). On my machine, with 25.1.0, the tests took 140 seconds, compared to 350 seconds with 25.2.7. I am running Windows, I believe the effect is less noticeable (but still there) on mac/linux.
Last working version
Worked up to version: 25.1.0
Stopped working in version: somewhere between 25.1.0 and 25.2.7
To Reproduce
Steps to reproduce the behavior: Update from jest 25.1.0 to 25.2.7
Expected behavior
Test run time should not increase drastically when upgrading versions.
Link to repl or repo (highly encouraged)
I don't have a publicly available repo to show, but here are some performance charts I captured. I'm not familiar with the inner workings of Jest, but it appears the performance regression probably has something to do with the resolution of symlinks? We're not using any symlinks as far as I am aware.
25.1.0:
25.2.7:
Run
npx envinfo --preset jest
System:
OS: Windows 10 10.0.14393
CPU: (12) x64 Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
Binaries:
Node: 12.14.0 - C:\Program Files\nodejs\node.EXE
npm: 6.14.4 - C:\Users*****\AppData\Roaming\npm\npm.CMD
npmPackages:
jest: 25.2.7 => 25.2.7
Thank you for the work that you do maintaining Jest!
The text was updated successfully, but these errors were encountered: