From b14fd1a7202cd8001d1f6aed848445bad99ee15c Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Tue, 26 May 2015 13:23:26 +0200 Subject: [PATCH 1/2] lib: speed up require(), phase 1 Replace calls to fs.statSync() with an internal variant that does not create Error or Stat objects that put strain on the garbage collector. A secondary benefit is that it improves start-up times in the debugger because it no longer emits thousands of exception debug events. PR-URL: https://github.com/nodejs/io.js/pull/1801 Reviewed-By: Trevor Norris --- lib/module.js | 34 ++++++++++++++-------------------- src/node_file.cc | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/lib/module.js b/lib/module.js index 515ab6789ca658..c9e4e2c785b264 100644 --- a/lib/module.js +++ b/lib/module.js @@ -6,6 +6,7 @@ const runInThisContext = require('vm').runInThisContext; const assert = require('assert').ok; const fs = require('fs'); const path = require('path'); +const internalModuleStat = process.binding('fs').internalModuleStat; // If obj.hasOwnProperty has been overridden, then calling @@ -56,13 +57,6 @@ const debug = Module._debug; // -> a. // -> a/index. -function statPath(path) { - try { - return fs.statSync(path); - } catch (ex) {} - return false; -} - // check if the directory is a package.json dir const packageMainCache = {}; @@ -94,7 +88,7 @@ function tryPackage(requestPath, exts) { if (!pkg) return false; var filename = path.resolve(requestPath, pkg); - return tryFile(filename, null) || tryExtensions(filename, exts) || + return tryFile(filename) || tryExtensions(filename, exts) || tryExtensions(path.resolve(filename, 'index'), exts); } @@ -104,18 +98,19 @@ function tryPackage(requestPath, exts) { Module._realpathCache = {}; // check if the file exists and is not a directory -function tryFile(requestPath, stats) { - stats = stats || statPath(requestPath); - if (stats && !stats.isDirectory()) { - return fs.realpathSync(requestPath, Module._realpathCache); - } - return false; +function tryFile(requestPath) { + const rc = internalModuleStat(requestPath); + return rc === 0 && toRealPath(requestPath); +} + +function toRealPath(requestPath) { + return fs.realpathSync(requestPath, Module._realpathCache); } // given a path check a the file exists with any of the set extensions function tryExtensions(p, exts) { for (var i = 0, EL = exts.length; i < EL; i++) { - var filename = tryFile(p + exts[i], null); + var filename = tryFile(p + exts[i]); if (filename) { return filename; @@ -150,11 +145,10 @@ Module._findPath = function(request, paths) { var filename; if (!trailingSlash) { - var stats = statPath(basePath); - // try to join the request to the path - filename = tryFile(basePath, stats); - - if (!filename && stats && stats.isDirectory()) { + const rc = internalModuleStat(basePath); + if (rc === 0) { // File. + filename = toRealPath(basePath); + } else if (rc === 1) { // Directory. filename = tryPackage(basePath, exts); } diff --git a/src/node_file.cc b/src/node_file.cc index 71cf31829e859d..4e00f15e268112 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -433,6 +433,26 @@ Local BuildStatsObject(Environment* env, const uv_stat_t* s) { return handle_scope.Escape(stats); } +// Used to speed up module loading. Returns 0 if the path refers to +// a file, 1 when it's a directory or < 0 on error (usually -ENOENT.) +// The speedup comes from not creating thousands of Stat and Error objects. +static void InternalModuleStat(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK(args[0]->IsString()); + node::Utf8Value path(env->isolate(), args[0]); + + uv_fs_t req; + int rc = uv_fs_stat(env->event_loop(), &req, *path, nullptr); + if (rc == 0) { + const uv_stat_t* const s = static_cast(req.ptr); + rc = !!(s->st_mode & S_IFDIR); + } + uv_fs_req_cleanup(&req); + + args.GetReturnValue().Set(rc); +} + static void Stat(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -1141,6 +1161,7 @@ void InitFs(Handle target, env->SetMethod(target, "rmdir", RMDir); env->SetMethod(target, "mkdir", MKDir); env->SetMethod(target, "readdir", ReadDir); + env->SetMethod(target, "internalModuleStat", InternalModuleStat); env->SetMethod(target, "stat", Stat); env->SetMethod(target, "lstat", LStat); env->SetMethod(target, "fstat", FStat); From 1bbf8d0720a061e281718f9e6a542da4268160b8 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Tue, 26 May 2015 16:50:16 +0200 Subject: [PATCH 2/2] lib: speed up require(), phase 2 Replace calls to fs.readFileSync() with an internal variant that does not create Error objects on failure and is a bit speedier in general. A secondary benefit is that it improves start-up times in the debugger because it no longer emits thousands of exception debug events. On a medium-sized application[0], this commit and its predecessor reduce start-up times from about 1.5s to 0.5s and reduce the number of start-up exceptions from ~6100 to 32, half of them internal to the application. [0] https://github.com/strongloop/loopback-sample-app PR-URL: https://github.com/nodejs/io.js/pull/1801 Reviewed-By: Trevor Norris --- lib/module.js | 9 +++++---- src/node_file.cc | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/lib/module.js b/lib/module.js index c9e4e2c785b264..65e44b60b16b65 100644 --- a/lib/module.js +++ b/lib/module.js @@ -6,6 +6,7 @@ const runInThisContext = require('vm').runInThisContext; const assert = require('assert').ok; const fs = require('fs'); const path = require('path'); +const internalModuleReadFile = process.binding('fs').internalModuleReadFile; const internalModuleStat = process.binding('fs').internalModuleStat; @@ -65,10 +66,10 @@ function readPackage(requestPath) { return packageMainCache[requestPath]; } - try { - var jsonPath = path.resolve(requestPath, 'package.json'); - var json = fs.readFileSync(jsonPath, 'utf8'); - } catch (e) { + var jsonPath = path.resolve(requestPath, 'package.json'); + var json = internalModuleReadFile(jsonPath); + + if (json === undefined) { return false; } diff --git a/src/node_file.cc b/src/node_file.cc index 4e00f15e268112..c8696f1295474b 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -22,6 +22,8 @@ # include #endif +#include + namespace node { using v8::Array; @@ -433,6 +435,50 @@ Local BuildStatsObject(Environment* env, const uv_stat_t* s) { return handle_scope.Escape(stats); } +// Used to speed up module loading. Returns the contents of the file as +// a string or undefined when the file cannot be opened. The speedup +// comes from not creating Error objects on failure. +static void InternalModuleReadFile(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK(args[0]->IsString()); + node::Utf8Value path(env->isolate(), args[0]); + + FILE* const stream = fopen(*path, "rb"); + if (stream == nullptr) { + return; + } + + std::vector chars; + while (!ferror(stream)) { + const size_t kBlockSize = 32 << 10; + const size_t start = chars.size(); + chars.resize(start + kBlockSize); + const size_t numchars = fread(&chars[start], 1, kBlockSize, stream); + if (numchars < kBlockSize) { + chars.resize(start + numchars); + } + if (numchars == 0) { + break; + } + } + + CHECK_EQ(false, ferror(stream)); + CHECK_EQ(0, fclose(stream)); + + size_t start = 0; + if (chars.size() >= 3 && 0 == memcmp(&chars[0], "\xEF\xBB\xBF", 3)) { + start = 3; // Skip UTF-8 BOM. + } + + Local chars_string = + String::NewFromUtf8(env->isolate(), + &chars[start], + String::kNormalString, + chars.size() - start); + args.GetReturnValue().Set(chars_string); +} + // Used to speed up module loading. Returns 0 if the path refers to // a file, 1 when it's a directory or < 0 on error (usually -ENOENT.) // The speedup comes from not creating thousands of Stat and Error objects. @@ -1161,6 +1207,7 @@ void InitFs(Handle target, env->SetMethod(target, "rmdir", RMDir); env->SetMethod(target, "mkdir", MKDir); env->SetMethod(target, "readdir", ReadDir); + env->SetMethod(target, "internalModuleReadFile", InternalModuleReadFile); env->SetMethod(target, "internalModuleStat", InternalModuleStat); env->SetMethod(target, "stat", Stat); env->SetMethod(target, "lstat", LStat);