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: https://github.com/nodejs/node/pull/4575
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Ben Noordhuis 2016-01-07 22:36:30 +01:00 committed by James M Snell
parent 809bf5e38c
commit 83f8d98806
5 changed files with 62 additions and 6 deletions

View File

@ -1,6 +1,8 @@
'use strict'; 'use strict';
module.exports = { makeRequireFunction, stripBOM }; exports = module.exports = { makeRequireFunction, stripBOM };
exports.requireDepth = 0;
// Invoke with makeRequireFunction.call(module) where |module| is the // Invoke with makeRequireFunction.call(module) where |module| is the
// Module object to use as the context for the require() function. // Module object to use as the context for the require() function.
@ -9,7 +11,12 @@ function makeRequireFunction() {
const self = this; const self = this;
function require(path) { function require(path) {
return self.require(path); try {
exports.requireDepth += 1;
return self.require(path);
} finally {
exports.requireDepth -= 1;
}
} }
require.resolve = function(request) { require.resolve = function(request) {

View File

@ -33,6 +33,20 @@ function tryWrapper(wrapper, opts) {
} }
function stat(filename) {
filename = path._makeLong(filename);
const cache = stat.cache;
if (cache !== null) {
const result = cache.get(filename);
if (result !== undefined) return result;
}
const result = internalModuleStat(filename);
if (cache !== null) cache.set(filename, result);
return result;
}
stat.cache = null;
function Module(id, parent) { function Module(id, parent) {
this.id = id; this.id = id;
this.exports = {}; this.exports = {};
@ -114,7 +128,7 @@ Module._realpathCache = {};
// check if the file exists and is not a directory // check if the file exists and is not a directory
function tryFile(requestPath) { function tryFile(requestPath) {
const rc = internalModuleStat(path._makeLong(requestPath)); const rc = stat(requestPath);
return rc === 0 && toRealPath(requestPath); return rc === 0 && toRealPath(requestPath);
} }
@ -151,12 +165,12 @@ Module._findPath = function(request, paths) {
// For each path // For each path
for (var i = 0, PL = paths.length; i < PL; i++) { for (var i = 0, PL = paths.length; i < PL; i++) {
// Don't search further if path doesn't exist // Don't search further if path doesn't exist
if (paths[i] && internalModuleStat(path._makeLong(paths[i])) < 1) continue; if (paths[i] && stat(paths[i]) < 1) continue;
var basePath = path.resolve(paths[i], request); var basePath = path.resolve(paths[i], request);
var filename; var filename;
if (!trailingSlash) { if (!trailingSlash) {
const rc = internalModuleStat(path._makeLong(basePath)); const rc = stat(basePath);
if (rc === 0) { // File. if (rc === 0) { // File.
filename = toRealPath(basePath); filename = toRealPath(basePath);
} else if (rc === 1) { // Directory. } else if (rc === 1) { // Directory.
@ -404,7 +418,11 @@ Module.prototype._compile = function(content, filename) {
const dirname = path.dirname(filename); const dirname = path.dirname(filename);
const require = internalModule.makeRequireFunction.call(this); const require = internalModule.makeRequireFunction.call(this);
const args = [this.exports, require, this, filename, dirname]; const args = [this.exports, require, this, filename, dirname];
return compiledWrapper.apply(this.exports, args); const depth = internalModule.requireDepth;
if (depth === 0) stat.cache = new Map();
const result = compiledWrapper.apply(this.exports, args);
if (depth === 0) stat.cache = null;
return result;
}; };

View File

@ -0,0 +1,9 @@
// Flags: --expose_internals
'use strict';
const assert = require('assert');
const internalModule = require('internal/module');
exports.requireDepth = internalModule.requireDepth;
assert.strictEqual(internalModule.requireDepth, 1);
assert.deepStrictEqual(require('./two'), { requireDepth: 2 });
assert.strictEqual(internalModule.requireDepth, 1);

View File

@ -0,0 +1,9 @@
// Flags: --expose_internals
'use strict';
const assert = require('assert');
const internalModule = require('internal/module');
exports.requireDepth = internalModule.requireDepth;
assert.strictEqual(internalModule.requireDepth, 2);
assert.deepStrictEqual(require('./one'), { requireDepth: 1 });
assert.strictEqual(internalModule.requireDepth, 2);

View File

@ -0,0 +1,13 @@
// Flags: --expose_internals
'use strict';
const common = require('../common');
const assert = require('assert');
const internalModule = require('internal/module');
// Module one loads two too so the expected depth for two is, well, two.
assert.strictEqual(internalModule.requireDepth, 0);
const one = require(common.fixturesDir + '/module-require-depth/one');
const two = require(common.fixturesDir + '/module-require-depth/two');
assert.deepStrictEqual(one, { requireDepth: 1 });
assert.deepStrictEqual(two, { requireDepth: 2 });
assert.strictEqual(internalModule.requireDepth, 0);