lib: speed up require(), phase 1
authorBen Noordhuis <info@bnoordhuis.nl>
Tue, 26 May 2015 11:23:26 +0000 (13:23 +0200)
committerBen Noordhuis <info@bnoordhuis.nl>
Wed, 27 May 2015 19:21:01 +0000 (21:21 +0200)
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 <trev.norris@gmail.com>
lib/module.js
src/node_file.cc

index 515ab67..c9e4e2c 100644 (file)
@@ -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.<ext>
 //   -> a/index.<ext>
 
-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);
       }
 
index 71cf318..4e00f15 100644 (file)
@@ -433,6 +433,26 @@ Local<Value> 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<Value>& 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<const uv_stat_t*>(req.ptr);
+    rc = !!(s->st_mode & S_IFDIR);
+  }
+  uv_fs_req_cleanup(&req);
+
+  args.GetReturnValue().Set(rc);
+}
+
 static void Stat(const FunctionCallbackInfo<Value>& args) {
   Environment* env = Environment::GetCurrent(args);
 
@@ -1141,6 +1161,7 @@ void InitFs(Handle<Object> 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);