Skip to content

Commit ae18bbe

Browse files
committed
lib: improve module loading performance
This commit improves module loading performance by at least ~25-35% in the module-loader benchmarks. Some optimization strategies include: * Try-finally/try-catch isolation * Replacing regular expressions with manual parsing * Avoiding unnecessary string and array creation * Avoiding constant recompilation of anonymous functions and function definitions within functions PR-URL: nodejs#5172 Reviewed-By: James M Snell <[email protected]>
1 parent 1e4674a commit ae18bbe

11 files changed

+274
-122
lines changed

lib/fs.js

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,42 @@ function tryToString(buf, encoding, callback) {
450450
callback(e, buf);
451451
}
452452

453+
function tryStatSync(fd, isUserFd) {
454+
var threw = true;
455+
var st;
456+
try {
457+
st = fs.fstatSync(fd);
458+
threw = false;
459+
} finally {
460+
if (threw && !isUserFd) fs.closeSync(fd);
461+
}
462+
return st;
463+
}
464+
465+
function tryCreateBuffer(size, fd, isUserFd) {
466+
var threw = true;
467+
var buffer;
468+
try {
469+
buffer = Buffer.allocUnsafe(size);
470+
threw = false;
471+
} finally {
472+
if (threw && !isUserFd) fs.closeSync(fd);
473+
}
474+
return buffer;
475+
}
476+
477+
function tryReadSync(fd, isUserFd, buffer, pos, len) {
478+
var threw = true;
479+
var bytesRead;
480+
try {
481+
bytesRead = fs.readSync(fd, buffer, pos, len);
482+
threw = false;
483+
} finally {
484+
if (threw && !isUserFd) fs.closeSync(fd);
485+
}
486+
return bytesRead;
487+
}
488+
453489
fs.readFileSync = function(path, options) {
454490
if (!options) {
455491
options = { encoding: null, flag: 'r' };
@@ -466,57 +502,36 @@ fs.readFileSync = function(path, options) {
466502
var isUserFd = isFd(path); // file descriptor ownership
467503
var fd = isUserFd ? path : fs.openSync(path, flag, 0o666);
468504

469-
var st;
470-
var size;
471-
var threw = true;
472-
try {
473-
st = fs.fstatSync(fd);
474-
size = st.isFile() ? st.size : 0;
475-
threw = false;
476-
} finally {
477-
if (threw && !isUserFd) fs.closeSync(fd);
478-
}
479-
505+
var st = tryStatSync(fd, isUserFd);
506+
var size = st.isFile() ? st.size : 0;
480507
var pos = 0;
481508
var buffer; // single buffer with file data
482509
var buffers; // list for when size is unknown
483510

484511
if (size === 0) {
485512
buffers = [];
486513
} else {
487-
threw = true;
488-
try {
489-
buffer = Buffer.allocUnsafe(size);
490-
threw = false;
491-
} finally {
492-
if (threw && !isUserFd) fs.closeSync(fd);
493-
}
514+
buffer = tryCreateBuffer(size, fd, isUserFd);
494515
}
495516

496-
var done = false;
497517
var bytesRead;
498518

499-
while (!done) {
500-
threw = true;
501-
try {
502-
if (size !== 0) {
503-
bytesRead = fs.readSync(fd, buffer, pos, size - pos);
504-
} else {
505-
// the kernel lies about many files.
506-
// Go ahead and try to read some bytes.
507-
buffer = Buffer.allocUnsafe(8192);
508-
bytesRead = fs.readSync(fd, buffer, 0, 8192);
509-
if (bytesRead) {
510-
buffers.push(buffer.slice(0, bytesRead));
511-
}
519+
if (size !== 0) {
520+
do {
521+
bytesRead = tryReadSync(fd, isUserFd, buffer, pos, size - pos);
522+
pos += bytesRead;
523+
} while (bytesRead !== 0 && pos < size);
524+
} else {
525+
do {
526+
// the kernel lies about many files.
527+
// Go ahead and try to read some bytes.
528+
buffer = Buffer.allocUnsafe(8192);
529+
bytesRead = tryReadSync(fd, isUserFd, buffer, 0, 8192);
530+
if (bytesRead !== 0) {
531+
buffers.push(buffer.slice(0, bytesRead));
512532
}
513-
threw = false;
514-
} finally {
515-
if (threw && !isUserFd) fs.closeSync(fd);
516-
}
517-
518-
pos += bytesRead;
519-
done = (bytesRead === 0) || (size !== 0 && pos >= size);
533+
pos += bytesRead;
534+
} while (bytesRead !== 0);
520535
}
521536

522537
if (!isUserFd)

lib/internal/bootstrap_node.js

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -284,37 +284,45 @@
284284
};
285285
}
286286

287-
function evalScript(name) {
288-
var Module = NativeModule.require('module');
289-
var path = NativeModule.require('path');
290-
287+
function tryGetCwd(path) {
288+
var threw = true;
289+
var cwd;
291290
try {
292-
var cwd = process.cwd();
293-
} catch (e) {
294-
// getcwd(3) can fail if the current working directory has been deleted.
295-
// Fall back to the directory name of the (absolute) executable path.
296-
// It's not really correct but what are the alternatives?
297-
cwd = path.dirname(process.execPath);
291+
cwd = process.cwd();
292+
threw = false;
293+
} finally {
294+
if (threw) {
295+
// getcwd(3) can fail if the current working directory has been deleted.
296+
// Fall back to the directory name of the (absolute) executable path.
297+
// It's not really correct but what are the alternatives?
298+
return path.dirname(process.execPath);
299+
}
298300
}
301+
return cwd;
302+
}
303+
304+
function evalScript(name) {
305+
const Module = NativeModule.require('module');
306+
const path = NativeModule.require('path');
307+
const cwd = tryGetCwd(path);
299308

300-
var module = new Module(name);
309+
const module = new Module(name);
301310
module.filename = path.join(cwd, name);
302311
module.paths = Module._nodeModulePaths(cwd);
303-
var script = process._eval;
304-
var body = script;
305-
script = `global.__filename = ${JSON.stringify(name)};\n` +
306-
'global.exports = exports;\n' +
307-
'global.module = module;\n' +
308-
'global.__dirname = __dirname;\n' +
309-
'global.require = require;\n' +
310-
'return require("vm").runInThisContext(' +
311-
`${JSON.stringify(body)}, { filename: ` +
312-
`${JSON.stringify(name)}, displayErrors: true });\n`;
312+
const body = process._eval;
313+
const script = `global.__filename = ${JSON.stringify(name)};\n` +
314+
'global.exports = exports;\n' +
315+
'global.module = module;\n' +
316+
'global.__dirname = __dirname;\n' +
317+
'global.require = require;\n' +
318+
'return require("vm").runInThisContext(' +
319+
`${JSON.stringify(body)}, { filename: ` +
320+
`${JSON.stringify(name)}, displayErrors: true });\n`;
313321
// Defer evaluation for a tick. This is a workaround for deferred
314322
// events not firing when evaluating scripts from the command line,
315323
// see https://github.com/nodejs/node/issues/1600.
316324
process.nextTick(function() {
317-
var result = module._compile(script, `${name}-wrapper`);
325+
const result = module._compile(script, `${name}-wrapper`);
318326
if (process._print_eval) console.log(result);
319327
});
320328
}

lib/internal/module.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ function makeRequireFunction() {
1919
}
2020
}
2121

22-
require.resolve = function(request) {
22+
function resolve(request) {
2323
return Module._resolveFilename(request, self);
24-
};
24+
}
25+
26+
require.resolve = resolve;
2527

2628
require.main = process.mainModule;
2729

0 commit comments

Comments
 (0)