Skip to content

Commit 2ffa9c5

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: #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 07d5dd2 commit 2ffa9c5

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,
@@ -292,24 +293,46 @@ async function readFileHandle(filehandle, options) {
292293
if (size > kIoMaxLength)
293294
throw new ERR_FS_FILE_TOO_LARGE(size);
294295

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

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

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

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)