fs: add native fast paths for recursive readdir and buffer I/O - #64396
fs: add native fast paths for recursive readdir and buffer I/O#64396anonrig wants to merge 7 commits into
Conversation
|
format cpp ;) |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #64396 +/- ##
==========================================
- Coverage 90.11% 90.08% -0.04%
==========================================
Files 752 752
Lines 251569 251845 +276
Branches 47268 47349 +81
==========================================
+ Hits 226701 226863 +162
- Misses 16233 16265 +32
- Partials 8635 8717 +82
🚀 New features to boost your workflow:
|
| return binding.readFileUtf8(path, stringToFlags(options.flag)); | ||
| const flags = stringToFlags(options.flag); | ||
| // C++ fast paths: single native call instead of open/stat/read/close. | ||
| if (options.encoding === 'utf8' || options.encoding === 'utf-8') { |
There was a problem hiding this comment.
Could these not also be uppercase? #64341 adds a new isUtf8Encoding to avoid missing valid encoding strings. Maybe a new lint rule would help avoid missing these.
There was a problem hiding this comment.
Done. Added isUtf8Encoding() that uses normalizeEncoding(), so the C++ fast paths also accept UTF8, UTF-8, and mixed-case aliases (not only lowercase utf8/utf-8).
| if (offset < static_cast<size_t>(size)) { | ||
| // Truncate logical length without reallocating when the file shrank. | ||
| args.GetReturnValue().Set( | ||
| Buffer::Copy(isolate, data, offset).ToLocalChecked()); |
There was a problem hiding this comment.
Let's not use ToLocalChecked().
There was a problem hiding this comment.
Done — ReadFileBuffer now uses ToLocal() and returns on allocation failure instead of ToLocalChecked().
Move readdirSync({ recursive: true }) into a C++ walk that uses
scandir dirent types so most entries avoid an extra stat(). Add
readFileBuffer and writeFileBuffer bindings so Buffer readFileSync
and writeFileSync avoid open/stat/read/write/close round-trips in JS,
matching the existing utf8 fast paths. Throw ERR_FS_FILE_TOO_LARGE
from C++ when the file exceeds 2 GiB.
On a ~2k-entry tree, recursive readdir is about 2x faster than the
JS walk; buffer read/write see smaller single-digit gains.
Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
Always call the native readdirRecursiveSync binding and remove the unused JS fallback. Document readFileBuffer, writeFileBuffer, and readdirRecursiveSync on the fs internal binding typings. Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
Replace the extra S_IS* macros with a switch on S_IFMT, avoid a temporary string when encoding relative paths, and tighten the directory-descent type checks. Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
Use primordials Array instead of the global, return undefined explicitly for the JSDoc check, and match clang-format on the directory descent condition. Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
The C++ readdirRecursiveSync fast path handles Buffer paths, so test-fs-readdir-sync-recursive-with-buffer now passes. Known-issues tests are expected to fail; a passing negative test fails CI. Also avoid ToLocalChecked() in ReadFileBuffer and drop the always-true st_size >= 0 comparison. Refs: nodejs#58892
5a4bf75 to
bdaef34
Compare
The node-core eslint rules require test files to load common first.
Use normalizeEncoding so readFileSync/writeFileSync take the C++ fast path for UTF8, UTF-8, and mixed-case variants, not only lowercase utf8/utf-8. Refs: nodejs#64341
|
Large number of directly relevant failures on Windows. |
Summary
Adds C++ fast paths for common
fssync operations, following the same pattern as the existingreadFileUtf8/writeFileUtf8bindings:fs.readdirSync(path, { recursive: true })— full directory walk in C++ (readdirRecursiveSync). Uses scandir dirent types so most entries skip the extrainternalModuleStatthe JS walk does per entry. Symlinks /UV_DIRENT_UNKNOWNstillstat/lstatas needed for correct descent andDirenttypes.fs.readFileSync(path)/encoding: null— single nativereadFileBuffer(open / fstat / read / close). ThrowsERR_FS_FILE_TOO_LARGEwhen size exceeds 2 GiB.fs.writeFileSync(path, buffer)— single nativewriteFileBufferforArrayBufferViewdata whenflushis not set.Public API and results (sorted) stay the same; sequential/parallel readdir recursive and read/write file tests pass.
Benchmarks (arm64 macOS, Release)
Tree: ~2110 entries. Short runs (
n=25for readdir).readdirRecursiveSyncreaddir+internalModuleStatfs.readdirSync({ recursive: true })withFileTypesBuffer I/O (same binary, short
n):readFileSyncsmallreadFileSync64 KiBwriteFileSync1 KiB bufferLarger trees show recursive readdir closer to ~3× (stats dominate).
Test plan
test-fs-readdir-recursive(parallel + sequential)test-fs-readdir-types/test-fs-readdir-types-symlinkstest-fs-readfile(+ empty, fd, flags, utf8 fast path)test-fs-write-file*(sync, buffer, typedarrays, flush)