-
Notifications
You must be signed in to change notification settings - Fork 30k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fs: include more fs.stat*() optimizations #11665
Conversation
For what it's worth, this will break some modules like mock-fs which rely on mutating I acknowledge that |
@not-an-aardvark Well, in a recent PR I was told not to worry about |
Fair enough. The reason I bring this up is that #11522 ended up causing tschaub/mock-fs#197 and breaking some builds. (I'm not trying to argue that this particular decision was justified/unjustified -- I just want to make sure that these decisions are made with full information on what they might break downstream.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with comments.
lib/fs.js
Outdated
@@ -114,6 +115,24 @@ function makeCallback(cb) { | |||
}; | |||
} | |||
|
|||
// Special case of `makeCallback()` that is specific to async `*stat()` calls as | |||
// an optimization, since the data passed back to the callback needs to be | |||
// transformed anyway |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing dot. Happens in quite a few other places. I won't point them out but please fix them up.
lib/fs.js
Outdated
// Use stats array directly to avoid creating an fs.Stats instance just for | ||
// our internal use | ||
var size; | ||
if (tryStatSync(fd, isUserFd) && ((statValues[1] & S_IFMT) === S_IFREG)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the extra parens?
lib/fs.js
Outdated
this._handle.onchange = function(current, previous, newStatus) { | ||
this._handle.onchange = function(newStatus) { | ||
var current = statsFromValues(); | ||
var previous = statsFromPrevValues(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could move this to after the if statement if you make it compare on the statValues entry directly.
lib/fs.js
Outdated
@@ -1665,7 +1714,8 @@ fs.realpath = function realpath(p, options, callback) { | |||
// dev/ino always return 0 on windows, so skip the check. | |||
let id; | |||
if (!isWindows) { | |||
id = `${stat.dev.toString(32)}:${stat.ino.toString(32)}`; | |||
// id === stats.dev:stats.ino | |||
id = `${statValues[0].toString(32)}:${statValues[7].toString(32)}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some field index constants would be good to have. Using number literals obscures it too much.
src/node_stat_watcher.cc
Outdated
Local<Value> argv[] = { | ||
BuildStatsObject(env, curr), | ||
BuildStatsObject(env, prev), | ||
Integer::New(env->isolate(), status) | ||
}; | ||
wrap->MakeCallback(env->onchange_string(), arraysize(argv), argv); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can simplify this now to:
Local<Value> arg = Integer::New(env->isolate(), status);
wrap->MakeCallback(env->onchange_string(), 1, &arg);
lib/fs.js
Outdated
binding.FSInitialize(fs.Stats); | ||
|
||
fs.Stats.prototype._checkModeProperty = function(property) { | ||
Stats.prototype._checkModeProperty = function(property) { | ||
return ((this.mode & constants.S_IFMT) === property); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can drop the constants.
now here.
lib/fs.js
Outdated
return this._checkModeProperty(constants.S_IFDIR); | ||
}; | ||
|
||
fs.Stats.prototype.isFile = function() { | ||
Stats.prototype.isFile = function() { | ||
return this._checkModeProperty(constants.S_IFREG); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto.
lib/fs.js
Outdated
return this._checkModeProperty(constants.S_IFCHR); | ||
}; | ||
|
||
fs.Stats.prototype.isSymbolicLink = function() { | ||
Stats.prototype.isSymbolicLink = function() { | ||
return this._checkModeProperty(constants.S_IFLNK); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And ditto.
src/node_file.cc
Outdated
ASYNC_CALL(fstat, args[1], UTF8, fd) | ||
} else { | ||
SYNC_CALL(fstat, 0, fd) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nullptr
lib/fs.js
Outdated
@@ -130,7 +149,7 @@ function isFd(path) { | |||
return (path >>> 0) === path; | |||
} | |||
|
|||
// Static method to set the stats properties on a Stats object. | |||
// Constructor for file stats | |||
function Stats( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw, it's worth noting that I had tried to convert this to a ES6 Class just to see what would happen and it yielded a 50% perf reduction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM once @bnoordhuis' comments are addressed
69c14fc
to
1fa23da
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with a suggestion
lib/fs.js
Outdated
if (oldStatus === -1 && | ||
newStatus === -1 && | ||
current.nlink === previous.nlink) return; | ||
statValues[2] === statValues[16]) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be good to leave a comment to indicate that these values refer to .nlink
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In another comment I suggested adding constants for the field indices, that would help with legibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed here as well now. I misunderstood the original suggestion for this line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with a suggestion.
lib/fs.js
Outdated
if (oldStatus === -1 && | ||
newStatus === -1 && | ||
current.nlink === previous.nlink) return; | ||
statValues[2] === statValues[16]) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In another comment I suggested adding constants for the field indices, that would help with legibility.
e072aa3
to
add40be
Compare
CI again after making suggested changes: https://ci.nodejs.org/job/node-test-pull-request/6719/ |
What I had in mind was more along the lines of |
@bnoordhuis I was trying to avoid lookups where possible as I'm not sure V8 will always inline the value, even if |
Valid concern. One more argument for reintroducing macros, I suppose. Alternatively, you could write them as |
We should warn them ahead of time still I think. @mscdex do you figure the new way is still shimmable? |
@Fishrock123 Support for the |
add40be
to
721a107
Compare
Rebased and added inline comments next to the Float64Array's indices. Any further comments/questions/suggestions @nodejs/collaborators? |
Including: * Move async *stat() functions to FillStatsArray() now used by the sync *stat() functions * Avoid creating fs.Stats instances for implicit async/sync *stat() calls used in various fs functions * Store reference to Float64Array data on C++ side for easier/faster access, instead of passing from JS to C++ on every async/sync *stat() call PR-URL: #11665 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Including: * Skip URL instance check for common (string) cases * Avoid regexp on non-Windows platforms when parsing the root of a path * Skip call to `getOptions()` in common case where no `options` is passed * Avoid `hasOwnProperty()` PR-URL: #11665 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Landed in 6a5ab5d...7109774 |
Including: * Move async *stat() functions to FillStatsArray() now used by the sync *stat() functions * Avoid creating fs.Stats instances for implicit async/sync *stat() calls used in various fs functions * Store reference to Float64Array data on C++ side for easier/faster access, instead of passing from JS to C++ on every async/sync *stat() call PR-URL: nodejs#11665 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Including: * Skip URL instance check for common (string) cases * Avoid regexp on non-Windows platforms when parsing the root of a path * Skip call to `getOptions()` in common case where no `options` is passed * Avoid `hasOwnProperty()` PR-URL: nodejs#11665 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
fields = new double[2 * 14]; | ||
env->set_fs_stats_field_array(fields); | ||
} | ||
Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mscdex , Correct me if i am wrong, but ArrayBuffer::New()
creates a buffer by default in externalizeMode. Who frees fields
array in that case? May be you want to pass ArrayBufferCreationMode::kExternalized
to this API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is the default mode already? fields
should never be freed by V8 according to the description, which is what we want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, but then how/when do we delete fields
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be you want to pass ArrayBufferCreationMode::kExternalized to this API?
What i meant was to pass ArrayBufferCreationMode::kInternalized
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't. It's created once and stays for the life of the process, for whenever someone calls the fs.stat*()
functions implicitly or explicitly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it.
@nodejs/ctc How do you feel about the semver-major-ness of these changes? Do you think it is warranted? The reason I ask is that apparently in some situations certain So either we could decide this PR really isn't semver-major and backport it as-is to v7.x which still has async Thoughts? |
@addaleax Ah ok I did not know it made it into v6.x. |
If there have been no regressions I'm good with downgrading thing to semver-minor and backporting. |
@jasnell Have enough people tested with master though since this was landed? |
Unsure. Preferably it would sit through at least one release on current before backporting. |
I think we'd need to make a decision sooner than that because of the issue I linked to. |
Doh, actually nevermind lol. For some reason I was reading that as backporting to v6. I'm good with backporting to v7. |
Well it's the same situation for v6 really, unless we want to decide differently for that branch. |
Yep, understood. Let's get it into a v7 release. Give it a week or two, then decide for v6. |
Including: * Move async *stat() functions to FillStatsArray() now used by the sync *stat() functions * Avoid creating fs.Stats instances for implicit async/sync *stat() calls used in various fs functions * Store reference to Float64Array data on C++ side for easier/faster access, instead of passing from JS to C++ on every async/sync *stat() call Backport-PR-URL: nodejs#11665 Fixes: nodejs#16496
Including: * Avoid regexp on non-Windows platforms when parsing the root of a path Backport-PR-URL: nodejs#11665
stat*()
optimizations including:Move async
*stat()
functions toFillStatsArray()
now used by the sync*stat()
functionsAvoid creating
fs.Stats
instances for implicit async/sync*stat()
calls used in variousfs
functionsStore reference to
Float64Array
data on C++ side for easier/faster access, instead of passing from JS to C++ on every async/sync*stat()
callAdditionally, there are some further
realpath*()
optimizations, including:Skip URL instance check for common (string) cases
Avoid regexp on non-Windows platforms when parsing the root of a path
Skip call to
getOptions()
in common case where nooptions
is passedAvoid
hasOwnProperty()
Some benchmark results with all of the changes in this PR:
I'm initially marking this as semver-major because of the changes in
fs.readFile()
,fs.readFileSync()
, etc. in how they retrieve stats for files/directories. If someone monkey patchesfs.*sync*()
, those monkey matched methods would no longer be called from those changed functions (fs.readFile()
, etc.). If we feel we shouldn't be worried about this, then I will gladly remove the label.CI: https://ci.nodejs.org/job/node-test-pull-request/6674/
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)