Skip to content

Commit b277776

Browse files
LinkgoronMylesBorins
authored andcommitted
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 Refs: #37583 PR-URL: #37608 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 180e3f6 commit b277776

File tree

2 files changed

+45
-20
lines changed

2 files changed

+45
-20
lines changed

Diff for: lib/internal/fs/promises.js

+35-15
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
// See https://github.com/libuv/libuv/pull/1501.
55
const kIoMaxLength = 2 ** 31 - 1;
66

7-
// Note: This is different from kReadFileBufferLength used for non-promisified
8-
// fs.readFile.
9-
const kReadFileMaxChunkSize = 2 ** 14;
7+
const kReadFileBufferLength = 512 * 1024;
8+
const kReadFileUnknownBufferLength = 64 * 1024;
109
const kWriteFileMaxChunkSize = 2 ** 14;
1110

1211
const {
@@ -316,25 +315,46 @@ async function readFileHandle(filehandle, options) {
316315
if (size > kIoMaxLength)
317316
throw new ERR_FS_FILE_TOO_LARGE(size);
318317

319-
const chunks = [];
320-
let isFirstChunk = true;
321-
const firstChunkSize = size === 0 ? kReadFileMaxChunkSize : size;
322-
const chunkSize = MathMin(firstChunkSize, kReadFileMaxChunkSize);
323318
let endOfFile = false;
319+
let totalRead = 0;
320+
const noSize = size === 0;
321+
const buffers = [];
322+
const fullBuffer = noSize ? undefined : Buffer.allocUnsafeSlow(size);
324323
do {
325324
if (signal?.aborted) {
326325
throw lazyDOMException('The operation was aborted', 'AbortError');
327326
}
328-
const buf = Buffer.alloc(isFirstChunk ? firstChunkSize : chunkSize);
329-
const { bytesRead, buffer } =
330-
await read(filehandle, buf, 0, buf.length, -1);
331-
endOfFile = bytesRead === 0;
332-
if (bytesRead > 0)
333-
ArrayPrototypePush(chunks, buffer.slice(0, bytesRead));
334-
isFirstChunk = false;
327+
let buffer;
328+
let offset;
329+
let length;
330+
if (noSize) {
331+
buffer = Buffer.allocUnsafeSlow(kReadFileUnknownBufferLength);
332+
offset = 0;
333+
length = kReadFileUnknownBufferLength;
334+
} else {
335+
buffer = fullBuffer;
336+
offset = totalRead;
337+
length = MathMin(size - totalRead, kReadFileBufferLength);
338+
}
339+
340+
const bytesRead = (await binding.read(filehandle.fd, buffer, offset,
341+
length, -1, kUsePromises)) || 0;
342+
totalRead += bytesRead;
343+
endOfFile = bytesRead === 0 || totalRead === size;
344+
if (noSize && bytesRead > 0) {
345+
const isBufferFull = bytesRead === kReadFileUnknownBufferLength;
346+
const chunkBuffer = isBufferFull ? buffer : buffer.slice(0, bytesRead);
347+
ArrayPrototypePush(buffers, chunkBuffer);
348+
}
335349
} while (!endOfFile);
336350

337-
const result = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks);
351+
let result;
352+
if (size > 0) {
353+
result = totalRead === size ? fullBuffer : fullBuffer.slice(0, totalRead);
354+
} else {
355+
result = buffers.length === 1 ? buffers[0] : Buffer.concat(buffers,
356+
totalRead);
357+
}
338358

339359
return options.encoding ? result.toString(options.encoding) : result;
340360
}

Diff for: test/parallel/test-fs-promises-file-handle-readFile.js

+10-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const {
1010
open,
1111
readFile,
1212
writeFile,
13-
truncate
13+
truncate,
1414
} = fs.promises;
1515
const path = require('path');
1616
const tmpdir = require('../common/tmpdir');
@@ -64,6 +64,7 @@ async function doReadAndCancel() {
6464
await assert.rejects(readFile(fileHandle, { signal }), {
6565
name: 'AbortError'
6666
});
67+
await fileHandle.close();
6768
}
6869

6970
// Signal aborted on first tick
@@ -74,10 +75,11 @@ async function doReadAndCancel() {
7475
fs.writeFileSync(filePathForHandle, buffer);
7576
const controller = new AbortController();
7677
const { signal } = controller;
77-
tick(1, () => controller.abort());
78+
process.nextTick(() => controller.abort());
7879
await assert.rejects(readFile(fileHandle, { signal }), {
7980
name: 'AbortError'
80-
});
81+
}, 'tick-0');
82+
await fileHandle.close();
8183
}
8284

8385
// Signal aborted right before buffer read
@@ -90,10 +92,12 @@ async function doReadAndCancel() {
9092

9193
const controller = new AbortController();
9294
const { signal } = controller;
93-
tick(2, () => controller.abort());
95+
tick(1, () => controller.abort());
9496
await assert.rejects(fileHandle.readFile({ signal, encoding: 'utf8' }), {
9597
name: 'AbortError'
96-
});
98+
}, 'tick-1');
99+
100+
await fileHandle.close();
97101
}
98102

99103
// Validate file size is within range for reading
@@ -111,6 +115,7 @@ async function doReadAndCancel() {
111115
name: 'RangeError',
112116
code: 'ERR_FS_FILE_TOO_LARGE'
113117
});
118+
await fileHandle.close();
114119
}
115120
}
116121

0 commit comments

Comments
 (0)