Skip to content

Commit eb2b8c6

Browse files
bnoordhuisevanlucas
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 <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 14061c6 commit eb2b8c6

File tree

5 files changed

+62
-6
lines changed

5 files changed

+62
-6
lines changed

lib/internal/module.js

Lines changed: 9 additions & 2 deletions
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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ function hasOwnProperty(obj, prop) {
2323
}
2424

2525

26+
function stat(filename) {
27+
filename = path._makeLong(filename);
28+
const cache = stat.cache;
29+
if (cache !== null) {
30+
const result = cache.get(filename);
31+
if (result !== undefined) return result;
32+
}
33+
const result = internalModuleStat(filename);
34+
if (cache !== null) cache.set(filename, result);
35+
return result;
36+
}
37+
stat.cache = null;
38+
39+
2640
function Module(id, parent) {
2741
this.id = id;
2842
this.exports = {};
@@ -104,7 +118,7 @@ Module._realpathCache = {};
104118

105119
// check if the file exists and is not a directory
106120
function tryFile(requestPath) {
107-
const rc = internalModuleStat(path._makeLong(requestPath));
121+
const rc = stat(requestPath);
108122
return rc === 0 && toRealPath(requestPath);
109123
}
110124

@@ -141,12 +155,12 @@ Module._findPath = function(request, paths) {
141155
// For each path
142156
for (var i = 0, PL = paths.length; i < PL; i++) {
143157
// Don't search further if path doesn't exist
144-
if (paths[i] && internalModuleStat(path._makeLong(paths[i])) < 1) continue;
158+
if (paths[i] && stat(paths[i]) < 1) continue;
145159
var basePath = path.resolve(paths[i], request);
146160
var filename;
147161

148162
if (!trailingSlash) {
149-
const rc = internalModuleStat(path._makeLong(basePath));
163+
const rc = stat(basePath);
150164
if (rc === 0) { // File.
151165
filename = toRealPath(basePath);
152166
} else if (rc === 1) { // Directory.
@@ -394,7 +408,11 @@ Module.prototype._compile = function(content, filename) {
394408
const dirname = path.dirname(filename);
395409
const require = internalModule.makeRequireFunction.call(this);
396410
const args = [this.exports, require, this, filename, dirname];
397-
return compiledWrapper.apply(this.exports, args);
411+
const depth = internalModule.requireDepth;
412+
if (depth === 0) stat.cache = new Map();
413+
const result = compiledWrapper.apply(this.exports, args);
414+
if (depth === 0) stat.cache = null;
415+
return result;
398416
};
399417

400418

Lines changed: 9 additions & 0 deletions
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);
Lines changed: 9 additions & 0 deletions
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);
Lines changed: 13 additions & 0 deletions
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)