Skip to content

Commit 83f8d98

Browse files
bnoordhuisjasnell
authored andcommitted
module: cache stat() results more aggressively
Reduce the number of stat() system calls that require() makes by caching the results more aggressively. To avoid unbounded growth without implementing a LRU cache, scope the cache to the lifetime of the first call to require(). Recursive calls (i.e. require() calls in the included code) transparently profit from the cache. The benchmarked application is the loopback-sample-app[0] and it sees the number of stat calls at start-up go down by 40%, from 4736 to 2810. [0] https://github.com/strongloop/loopback-sample-app PR-URL: #4575 Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 809bf5e commit 83f8d98

File tree

5 files changed

+62
-6
lines changed

5 files changed

+62
-6
lines changed

lib/internal/module.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
'use strict';
22

3-
module.exports = { makeRequireFunction, stripBOM };
3+
exports = module.exports = { makeRequireFunction, stripBOM };
4+
5+
exports.requireDepth = 0;
46

57
// Invoke with makeRequireFunction.call(module) where |module| is the
68
// Module object to use as the context for the require() function.
@@ -9,7 +11,12 @@ function makeRequireFunction() {
911
const self = this;
1012

1113
function require(path) {
12-
return self.require(path);
14+
try {
15+
exports.requireDepth += 1;
16+
return self.require(path);
17+
} finally {
18+
exports.requireDepth -= 1;
19+
}
1320
}
1421

1522
require.resolve = function(request) {

lib/module.js

+22-4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ function tryWrapper(wrapper, opts) {
3333
}
3434

3535

36+
function stat(filename) {
37+
filename = path._makeLong(filename);
38+
const cache = stat.cache;
39+
if (cache !== null) {
40+
const result = cache.get(filename);
41+
if (result !== undefined) return result;
42+
}
43+
const result = internalModuleStat(filename);
44+
if (cache !== null) cache.set(filename, result);
45+
return result;
46+
}
47+
stat.cache = null;
48+
49+
3650
function Module(id, parent) {
3751
this.id = id;
3852
this.exports = {};
@@ -114,7 +128,7 @@ Module._realpathCache = {};
114128

115129
// check if the file exists and is not a directory
116130
function tryFile(requestPath) {
117-
const rc = internalModuleStat(path._makeLong(requestPath));
131+
const rc = stat(requestPath);
118132
return rc === 0 && toRealPath(requestPath);
119133
}
120134

@@ -151,12 +165,12 @@ Module._findPath = function(request, paths) {
151165
// For each path
152166
for (var i = 0, PL = paths.length; i < PL; i++) {
153167
// Don't search further if path doesn't exist
154-
if (paths[i] && internalModuleStat(path._makeLong(paths[i])) < 1) continue;
168+
if (paths[i] && stat(paths[i]) < 1) continue;
155169
var basePath = path.resolve(paths[i], request);
156170
var filename;
157171

158172
if (!trailingSlash) {
159-
const rc = internalModuleStat(path._makeLong(basePath));
173+
const rc = stat(basePath);
160174
if (rc === 0) { // File.
161175
filename = toRealPath(basePath);
162176
} else if (rc === 1) { // Directory.
@@ -404,7 +418,11 @@ Module.prototype._compile = function(content, filename) {
404418
const dirname = path.dirname(filename);
405419
const require = internalModule.makeRequireFunction.call(this);
406420
const args = [this.exports, require, this, filename, dirname];
407-
return compiledWrapper.apply(this.exports, args);
421+
const depth = internalModule.requireDepth;
422+
if (depth === 0) stat.cache = new Map();
423+
const result = compiledWrapper.apply(this.exports, args);
424+
if (depth === 0) stat.cache = null;
425+
return result;
408426
};
409427

410428

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Flags: --expose_internals
2+
'use strict';
3+
const assert = require('assert');
4+
const internalModule = require('internal/module');
5+
6+
exports.requireDepth = internalModule.requireDepth;
7+
assert.strictEqual(internalModule.requireDepth, 1);
8+
assert.deepStrictEqual(require('./two'), { requireDepth: 2 });
9+
assert.strictEqual(internalModule.requireDepth, 1);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Flags: --expose_internals
2+
'use strict';
3+
const assert = require('assert');
4+
const internalModule = require('internal/module');
5+
6+
exports.requireDepth = internalModule.requireDepth;
7+
assert.strictEqual(internalModule.requireDepth, 2);
8+
assert.deepStrictEqual(require('./one'), { requireDepth: 1 });
9+
assert.strictEqual(internalModule.requireDepth, 2);
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Flags: --expose_internals
2+
'use strict';
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const internalModule = require('internal/module');
6+
7+
// Module one loads two too so the expected depth for two is, well, two.
8+
assert.strictEqual(internalModule.requireDepth, 0);
9+
const one = require(common.fixturesDir + '/module-require-depth/one');
10+
const two = require(common.fixturesDir + '/module-require-depth/two');
11+
assert.deepStrictEqual(one, { requireDepth: 1 });
12+
assert.deepStrictEqual(two, { requireDepth: 2 });
13+
assert.strictEqual(internalModule.requireDepth, 0);

0 commit comments

Comments
 (0)