Skip to content

Commit 132d78a

Browse files
LinkgoronMoritzLoewenstein
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: nodejs#37583 PR-URL: nodejs#37608 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent f90fd11 commit 132d78a

File tree

2 files changed

+43
-16
lines changed

2 files changed

+43
-16
lines changed

lib/internal/fs/promises.js

+33-11
Original file line numberDiff line numberDiff line change
@@ -291,24 +291,46 @@ async function readFileHandle(filehandle, options) {
291291
if (size > kIoMaxLength)
292292
throw new ERR_FS_FILE_TOO_LARGE(size);
293293

294-
const chunks = [];
295-
const chunkSize = size === 0 ?
296-
kReadFileMaxChunkSize :
297-
MathMin(size, kReadFileMaxChunkSize);
298294
let endOfFile = false;
295+
let totalRead = 0;
296+
const noSize = size === 0;
297+
const buffers = [];
298+
const fullBuffer = noSize ? undefined : Buffer.allocUnsafeSlow(size);
299299
do {
300300
if (signal && signal.aborted) {
301301
throw lazyDOMException('The operation was aborted', 'AbortError');
302302
}
303-
const buf = Buffer.alloc(chunkSize);
304-
const { bytesRead, buffer } =
305-
await read(filehandle, buf, 0, chunkSize, -1);
306-
endOfFile = bytesRead === 0;
307-
if (bytesRead > 0)
308-
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+
}
309325
} while (!endOfFile);
310326

311-
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+
}
312334

313335
return options.encoding ? result.toString(options.encoding) : result;
314336
}

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)