Skip to content

Commit b4fffb0

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: #37583 (Old) Backport-PR-URL: #37703 PR-URL: #37608
1 parent f0b7b93 commit b4fffb0

File tree

2 files changed

+65
-23
lines changed

2 files changed

+65
-23
lines changed

lib/internal/fs/promises.js

+41-23
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,7 @@
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 {
4+
ArrayPrototypePush,
165
Error,
176
MathMax,
187
MathMin,
@@ -44,6 +33,13 @@ const {
4433
const { isArrayBufferView } = require('internal/util/types');
4534
const { rimrafPromises } = require('internal/fs/rimraf');
4635
const {
36+
constants: {
37+
kIoMaxLength,
38+
kMaxUserId,
39+
kReadFileBufferLength,
40+
kReadFileUnknownBufferLength,
41+
kWriteFileMaxChunkSize,
42+
},
4743
copyObject,
4844
getDirents,
4945
getOptions,
@@ -296,24 +292,46 @@ async function readFileHandle(filehandle, options) {
296292
if (size > kIoMaxLength)
297293
throw new ERR_FS_FILE_TOO_LARGE(size);
298294

299-
const chunks = [];
300-
const chunkSize = size === 0 ?
301-
kReadFileMaxChunkSize :
302-
MathMin(size, kReadFileMaxChunkSize);
303295
let endOfFile = false;
296+
let totalRead = 0;
297+
const noSize = size === 0;
298+
const buffers = [];
299+
const fullBuffer = noSize ? undefined : Buffer.allocUnsafeSlow(size);
304300
do {
305301
if (signal && signal.aborted) {
306302
throw lazyDOMException('The operation was aborted', 'AbortError');
307303
}
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));
304+
let buffer;
305+
let offset;
306+
let length;
307+
if (noSize) {
308+
buffer = Buffer.allocUnsafeSlow(kReadFileUnknownBufferLength);
309+
offset = 0;
310+
length = kReadFileUnknownBufferLength;
311+
} else {
312+
buffer = fullBuffer;
313+
offset = totalRead;
314+
length = MathMin(size - totalRead, kReadFileBufferLength);
315+
}
316+
317+
const bytesRead = (await binding.read(filehandle.fd, buffer, offset,
318+
length, -1, kUsePromises)) || 0;
319+
totalRead += bytesRead;
320+
endOfFile = bytesRead === 0 || totalRead === size;
321+
if (noSize && bytesRead > 0) {
322+
const isBufferFull = bytesRead === kReadFileUnknownBufferLength;
323+
const chunkBuffer = isBufferFull ? buffer : buffer.slice(0, bytesRead);
324+
ArrayPrototypePush(buffers, chunkBuffer);
325+
}
314326
} while (!endOfFile);
315327

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

318336
return options.encoding ? result.toString(options.encoding) : result;
319337
}

lib/internal/fs/utils.js

+24
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)