Skip to content

Commit 879e54c

Browse files
fs: improve fsPromises readFile performance
Improve the fsPromises readFile performance by allocating only one buffer, when size is known, increase the size of the readbuffer chunks, and dont read more data if size bytes have been read. Also moves constants to internal/fs/utils. refs: nodejs#37583 (Old) Backport-PR-URL: nodejs#37703 PR-URL: nodejs#37608
1 parent f0b7b93 commit 879e54c

File tree

2 files changed

+64
-23
lines changed

2 files changed

+64
-23
lines changed

lib/internal/fs/promises.js

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,5 @@
11
'use strict';
22

3-
// Most platforms don't allow reads or writes >= 2 GB.
4-
// See https://github.com/libuv/libuv/pull/1501.
5-
const kIoMaxLength = 2 ** 31 - 1;
6-
7-
// Note: This is different from kReadFileBufferLength used for non-promisified
8-
// fs.readFile.
9-
const kReadFileMaxChunkSize = 2 ** 14;
10-
const kWriteFileMaxChunkSize = 2 ** 14;
11-
12-
// 2 ** 32 - 1
13-
const kMaxUserId = 4294967295;
14-
153
const {
164
Error,
175
MathMax,
@@ -44,6 +32,13 @@ const {
4432
const { isArrayBufferView } = require('internal/util/types');
4533
const { rimrafPromises } = require('internal/fs/rimraf');
4634
const {
35+
constants: {
36+
kIoMaxLength,
37+
kMaxUserId,
38+
kReadFileBufferLength,
39+
kReadFileUnknownBufferLength,
40+
kWriteFileMaxChunkSize,
41+
},
4742
copyObject,
4843
getDirents,
4944
getOptions,
@@ -296,24 +291,46 @@ async function readFileHandle(filehandle, options) {
296291
if (size > kIoMaxLength)
297292
throw new ERR_FS_FILE_TOO_LARGE(size);
298293

299-
const chunks = [];
300-
const chunkSize = size === 0 ?
301-
kReadFileMaxChunkSize :
302-
MathMin(size, kReadFileMaxChunkSize);
303294
let endOfFile = false;
295+
let totalRead = 0;
296+
const noSize = size === 0;
297+
const buffers = [];
298+
const fullBuffer = noSize ? undefined : Buffer.allocUnsafeSlow(size);
304299
do {
305300
if (signal && signal.aborted) {
306301
throw lazyDOMException('The operation was aborted', 'AbortError');
307302
}
308-
const buf = Buffer.alloc(chunkSize);
309-
const { bytesRead, buffer } =
310-
await read(filehandle, buf, 0, chunkSize, -1);
311-
endOfFile = bytesRead === 0;
312-
if (bytesRead > 0)
313-
chunks.push(buffer.slice(0, bytesRead));
303+
let buffer;
304+
let offset;
305+
let length;
306+
if (noSize) {
307+
buffer = Buffer.allocUnsafeSlow(kReadFileUnknownBufferLength);
308+
offset = 0;
309+
length = kReadFileUnknownBufferLength;
310+
} else {
311+
buffer = fullBuffer;
312+
offset = totalRead;
313+
length = MathMin(size - totalRead, kReadFileBufferLength);
314+
}
315+
316+
const bytesRead = (await binding.read(filehandle.fd, buffer, offset,
317+
length, -1, kUsePromises)) || 0;
318+
totalRead += bytesRead;
319+
endOfFile = bytesRead === 0 || totalRead === size;
320+
if (noSize && bytesRead > 0) {
321+
const isBufferFull = bytesRead === kReadFileUnknownBufferLength;
322+
const chunkBuffer = isBufferFull ? buffer : buffer.slice(0, bytesRead);
323+
ArrayPrototypePush(buffers, chunkBuffer);
324+
}
314325
} while (!endOfFile);
315326

316-
const result = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks);
327+
let result;
328+
if (size > 0) {
329+
result = totalRead === size ? fullBuffer : fullBuffer.slice(0, totalRead);
330+
} else {
331+
result = buffers.length === 1 ? buffers[0] : Buffer.concat(buffers,
332+
totalRead);
333+
}
317334

318335
return options.encoding ? result.toString(options.encoding) : result;
319336
}

lib/internal/fs/utils.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,23 @@ const kMaximumCopyMode = COPYFILE_EXCL |
113113
COPYFILE_FICLONE |
114114
COPYFILE_FICLONE_FORCE;
115115

116+
// Most platforms don't allow reads or writes >= 2 GB.
117+
// See https://github.com/libuv/libuv/pull/1501.
118+
const kIoMaxLength = 2 ** 31 - 1;
119+
120+
// Use 64kb in case the file type is not a regular file and thus do not know the
121+
// actual file size. Increasing the value further results in more frequent over
122+
// allocation for small files and consumes CPU time and memory that should be
123+
// used else wise.
124+
// Use up to 512kb per read otherwise to partition reading big files to prevent
125+
// blocking other threads in case the available threads are all in use.
126+
const kReadFileUnknownBufferLength = 64 * 1024;
127+
const kReadFileBufferLength = 512 * 1024;
128+
129+
const kWriteFileMaxChunkSize = 512 * 1024;
130+
131+
const kMaxUserId = 2 ** 32 - 1;
132+
116133
const isWindows = process.platform === 'win32';
117134

118135
let fs;
@@ -815,6 +832,13 @@ const validatePosition = hideStackFrames((position, name) => {
815832
});
816833

817834
module.exports = {
835+
constants: {
836+
kIoMaxLength,
837+
kMaxUserId,
838+
kReadFileBufferLength,
839+
kReadFileUnknownBufferLength,
840+
kWriteFileMaxChunkSize,
841+
},
818842
assertEncoding,
819843
BigIntStats, // for testing
820844
copyObject,

0 commit comments

Comments
 (0)