Skip to content

Commit 2b0e270

Browse files
Linkgorontargos
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 Backport-PR-URL: #39838 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent a4d6f78 commit 2b0e270

File tree

2 files changed

+44
-16
lines changed

2 files changed

+44
-16
lines changed

lib/internal/fs/promises.js

+34-11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const kWriteFileMaxChunkSize = 2 ** 14;
44

55
const {
6+
ArrayPrototypePush,
67
Error,
78
MathMax,
89
MathMin,
@@ -293,24 +294,46 @@ async function readFileHandle(filehandle, options) {
293294
if (size > kIoMaxLength)
294295
throw new ERR_FS_FILE_TOO_LARGE(size);
295296

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

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

315338
return options.encoding ? result.toString(options.encoding) : result;
316339
}

test/parallel/test-fs-promises-file-handle-readFile.js

+10-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const {
1111
open,
1212
readFile,
1313
writeFile,
14-
truncate
14+
truncate,
1515
} = fs.promises;
1616
const path = require('path');
1717
const tmpdir = require('../common/tmpdir');
@@ -65,6 +65,7 @@ async function doReadAndCancel() {
6565
await assert.rejects(readFile(fileHandle, { signal }), {
6666
name: 'AbortError'
6767
});
68+
await fileHandle.close();
6869
}
6970

7071
// Signal aborted on first tick
@@ -75,10 +76,11 @@ async function doReadAndCancel() {
7576
fs.writeFileSync(filePathForHandle, buffer);
7677
const controller = new AbortController();
7778
const { signal } = controller;
78-
tick(1, () => controller.abort());
79+
process.nextTick(() => controller.abort());
7980
await assert.rejects(readFile(fileHandle, { signal }), {
8081
name: 'AbortError'
81-
});
82+
}, 'tick-0');
83+
await fileHandle.close();
8284
}
8385

8486
// Signal aborted right before buffer read
@@ -91,10 +93,12 @@ async function doReadAndCancel() {
9193

9294
const controller = new AbortController();
9395
const { signal } = controller;
94-
tick(2, () => controller.abort());
96+
tick(1, () => controller.abort());
9597
await assert.rejects(fileHandle.readFile({ signal, encoding: 'utf8' }), {
9698
name: 'AbortError'
97-
});
99+
}, 'tick-1');
100+
101+
await fileHandle.close();
98102
}
99103

100104
// Validate file size is within range for reading
@@ -112,6 +116,7 @@ async function doReadAndCancel() {
112116
name: 'RangeError',
113117
code: 'ERR_FS_FILE_TOO_LARGE'
114118
});
119+
await fileHandle.close();
115120
}
116121
}
117122

0 commit comments

Comments
 (0)