Skip to content

Commit 6f22200

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 <benjamingr@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent 0ecc292 commit 6f22200

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
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const {
4+
ArrayPrototypePush,
45
Error,
56
MathMax,
67
MathMin,
@@ -291,24 +292,46 @@ async function readFileHandle(filehandle, options) {
291292
if (size > kIoMaxLength)
292293
throw new ERR_FS_FILE_TOO_LARGE(size);
293294

294-
const chunks = [];
295-
const chunkSize = size === 0 ?
296-
kReadFileMaxChunkSize :
297-
MathMin(size, kReadFileMaxChunkSize);
298295
let endOfFile = false;
296+
let totalRead = 0;
297+
const noSize = size === 0;
298+
const buffers = [];
299+
const fullBuffer = noSize ? undefined : Buffer.allocUnsafeSlow(size);
299300
do {
300301
if (signal && signal.aborted) {
301302
throw lazyDOMException('The operation was aborted', 'AbortError');
302303
}
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));
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+
}
309326
} while (!endOfFile);
310327

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

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

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)