From 22abb39b2cfc8a462caddf0abe6a096b8562380f Mon Sep 17 00:00:00 2001 From: Brian White Date: Thu, 23 Feb 2017 12:45:10 -0500 Subject: [PATCH] fs: improve performance for sync stat() functions PR-URL: https://github.com/nodejs/node/pull/11522 Reviewed-By: Ben Noordhuis Reviewed-By: James M Snell Reviewed-By: Anna Henningsen Reviewed-By: Matteo Collina Reviewed-By: Colin Ihrig --- benchmark/fs/bench-statSync.js | 32 +++++++++++---- lib/fs.js | 26 +++++++++--- src/node_file.cc | 72 +++++++++++++++++++++++++++------- 3 files changed, 102 insertions(+), 28 deletions(-) diff --git a/benchmark/fs/bench-statSync.js b/benchmark/fs/bench-statSync.js index ba1e8168b4aaf5..4bc2ecd65a3624 100644 --- a/benchmark/fs/bench-statSync.js +++ b/benchmark/fs/bench-statSync.js @@ -5,17 +5,35 @@ const fs = require('fs'); const bench = common.createBenchmark(main, { n: [1e4], - kind: ['lstatSync', 'statSync'] + kind: ['fstatSync', 'lstatSync', 'statSync'] }); function main(conf) { const n = conf.n >>> 0; - const fn = fs[conf.kind]; - - bench.start(); - for (var i = 0; i < n; i++) { - fn(__filename); + var fn; + var i; + switch (conf.kind) { + case 'statSync': + case 'lstatSync': + fn = fs[conf.kind]; + bench.start(); + for (i = 0; i < n; i++) { + fn(__filename); + } + bench.end(n); + break; + case 'fstatSync': + fn = fs.fstatSync; + const fd = fs.openSync(__filename, 'r'); + bench.start(); + for (i = 0; i < n; i++) { + fn(fd); + } + bench.end(n); + fs.closeSync(fd); + break; + default: + throw new Error('Invalid kind argument'); } - bench.end(n); } diff --git a/lib/fs.js b/lib/fs.js index d45bd82df7036c..3f2a3cc1ac4761 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -131,7 +131,7 @@ function isFd(path) { } // Static method to set the stats properties on a Stats object. -fs.Stats = function( +function Stats( dev, mode, nlink, @@ -160,7 +160,8 @@ fs.Stats = function( this.mtime = new Date(mtim_msec); this.ctime = new Date(ctim_msec); this.birthtime = new Date(birthtim_msec); -}; +} +fs.Stats = Stats; // Create a C++ binding to the function which creates a Stats object. binding.FSInitialize(fs.Stats); @@ -262,7 +263,7 @@ fs.existsSync = function(path) { try { handleError((path = getPathFromURL(path))); nullCheck(path); - binding.stat(pathModule._makeLong(path)); + binding.stat(pathModule._makeLong(path), statValues); return true; } catch (e) { return false; @@ -856,20 +857,33 @@ fs.stat = function(path, callback) { binding.stat(pathModule._makeLong(path), req); }; +const statValues = new Float64Array(14); +function statsFromValues() { + return new Stats(statValues[0], statValues[1], statValues[2], statValues[3], + statValues[4], statValues[5], + statValues[6] < 0 ? undefined : statValues[6], statValues[7], + statValues[8], statValues[9] < 0 ? undefined : statValues[9], + statValues[10], statValues[11], statValues[12], + statValues[13]); +} + fs.fstatSync = function(fd) { - return binding.fstat(fd); + binding.fstat(fd, statValues); + return statsFromValues(); }; fs.lstatSync = function(path) { handleError((path = getPathFromURL(path))); nullCheck(path); - return binding.lstat(pathModule._makeLong(path)); + binding.lstat(pathModule._makeLong(path), statValues); + return statsFromValues(); }; fs.statSync = function(path) { handleError((path = getPathFromURL(path))); nullCheck(path); - return binding.stat(pathModule._makeLong(path)); + binding.stat(pathModule._makeLong(path), statValues); + return statsFromValues(); }; fs.readlink = function(path, options, callback) { diff --git a/src/node_file.cc b/src/node_file.cc index 0abb88088786ae..99048b223478df 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -27,8 +27,10 @@ namespace node { using v8::Array; +using v8::ArrayBuffer; using v8::Context; using v8::EscapableHandleScope; +using v8::Float64Array; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; @@ -528,6 +530,37 @@ Local BuildStatsObject(Environment* env, const uv_stat_t* s) { return handle_scope.Escape(stats); } +void FillStatsArray(double* fields, const uv_stat_t* s) { + fields[0] = s->st_dev; + fields[1] = s->st_mode; + fields[2] = s->st_nlink; + fields[3] = s->st_uid; + fields[4] = s->st_gid; + fields[5] = s->st_rdev; +#if defined(__POSIX__) + fields[6] = s->st_blksize; +#else + fields[6] = -1; +#endif + fields[7] = s->st_ino; + fields[8] = s->st_size; +#if defined(__POSIX__) + fields[9] = s->st_blocks; +#else + fields[9] = -1; +#endif + // Dates. +#define X(idx, name) \ + fields[idx] = (static_cast(s->st_##name.tv_sec) * 1000) + \ + (static_cast(s->st_##name.tv_nsec / 1000000)); \ + + X(10, atim) + X(11, mtim) + X(12, ctim) + X(13, birthtim) +#undef X +} + // 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. @@ -612,12 +645,15 @@ static void Stat(const FunctionCallbackInfo& args) { BufferValue path(env->isolate(), args[0]); ASSERT_PATH(path) - if (args[1]->IsObject()) { - ASYNC_CALL(stat, args[1], UTF8, *path) - } else { + if (args[1]->IsFloat64Array()) { + Local array = args[1].As(); + CHECK_EQ(array->Length(), 14); + Local ab = array->Buffer(); + double* fields = static_cast(ab->GetContents().Data()); SYNC_CALL(stat, *path, *path) - args.GetReturnValue().Set( - BuildStatsObject(env, static_cast(SYNC_REQ.ptr))); + FillStatsArray(fields, static_cast(SYNC_REQ.ptr)); + } else if (args[1]->IsObject()) { + ASYNC_CALL(stat, args[1], UTF8, *path) } } @@ -630,12 +666,15 @@ static void LStat(const FunctionCallbackInfo& args) { BufferValue path(env->isolate(), args[0]); ASSERT_PATH(path) - if (args[1]->IsObject()) { - ASYNC_CALL(lstat, args[1], UTF8, *path) - } else { + if (args[1]->IsFloat64Array()) { + Local array = args[1].As(); + CHECK_EQ(array->Length(), 14); + Local ab = array->Buffer(); + double* fields = static_cast(ab->GetContents().Data()); SYNC_CALL(lstat, *path, *path) - args.GetReturnValue().Set( - BuildStatsObject(env, static_cast(SYNC_REQ.ptr))); + FillStatsArray(fields, static_cast(SYNC_REQ.ptr)); + } else if (args[1]->IsObject()) { + ASYNC_CALL(lstat, args[1], UTF8, *path) } } @@ -649,12 +688,15 @@ static void FStat(const FunctionCallbackInfo& args) { int fd = args[0]->Int32Value(); - if (args[1]->IsObject()) { - ASYNC_CALL(fstat, args[1], UTF8, fd) - } else { + if (args[1]->IsFloat64Array()) { + Local array = args[1].As(); + CHECK_EQ(array->Length(), 14); + Local ab = array->Buffer(); + double* fields = static_cast(ab->GetContents().Data()); SYNC_CALL(fstat, 0, fd) - args.GetReturnValue().Set( - BuildStatsObject(env, static_cast(SYNC_REQ.ptr))); + FillStatsArray(fields, static_cast(SYNC_REQ.ptr)); + } else if (args[1]->IsObject()) { + ASYNC_CALL(fstat, args[1], UTF8, fd) } }