performance: readFile in promises very slow
Version
v21.7.1
Platform
Darwin evgeniis.local 23.4.0 Darwin Kernel Version 23.4.0: Wed Feb 21 21:44:43 PST 2024; root:xnu-10063.101.15~2/RELEASE_ARM64_T6000 arm64
Subsystem
No response
What steps will reproduce the bug?
slow
const fs = require('fs/promises');
const start = Date.now();
let count = 0;
for (let i = 0; i < 10000; i++) {
fs.readFile("./text.txt", { encoding: 'utf-8' })
.then((data) => {
if (data !== "Hello, world") throw 1;
count++
if (count === 10000) {
console.log('time: ', Date.now() - start);
}
})
.catch((err) => {
throw 1
})
}
fast
const fs = require('fs');
const util = require('util');
const readFile = util.promisify(fs.readFile);
const start = Date.now();
let count = 0;
for (let i = 0; i < 10000; i++) {
readFile("./text.txt", { encoding: 'utf-8' })
.then((data) => {
if (data !== "Hello, world") throw 1;
count++
if (count === 10000) {
console.log('time: ', Date.now() - start);
}
})
.catch((err) => {
throw 1
})
}
How often does it reproduce? Is there a required condition?
No response
What is the expected behavior? Why is that the expected behavior?
No response
What do you see instead?
The promise version is 2 times slower. My tests showed 200ms vs 100ms
Additional information
No response
About this issue
- Original URL
- State: open
- Created 4 months ago
- Reactions: 2
- Comments: 25 (19 by maintainers)
That’s going to depend on the size of your file and if you’re program has other things to do – like @meyfa said, using
readFileSyncmight get you the content of the file slightly faster, but the tradeoff is that your program cannot do anything in the mean time.I think we are getting a bit off topic here… I would recommend all mentions of
readFileSyncshould be hidden as off topic.You can basically recover the performance by calling
readFileSyncwithin an async function…I suggest to transfer this issue to the performance team repo… what do you think @anonrig?
@Linkgoron @aduh95 you are right, this optimization was already done.
The problem remains because there are a lot of C++/JS transitions for large files, and the utf8 parsing still allocates double the memory.
Maybe I’m misunderstanding what you’re saying, but today the code uses fstat in-order to allocate the returned buffer. If it fails, it allocates 64kIb chunks for every read and then concats them in the end (note that even if fstat succeeds, Node still reads in 512 kib chunks into the larger buffer).
Anyhow I think it’s more important to optimize the utf8 case.
readFileSynchas to return the file contents synchronously, so it must read the whole file before returning. And since JS isn’t multi-threaded this will block execution.fs.promises.readFilecan allow other stuff to run while it fetches the file asynchronously, fulfilling the returned promise when done. The presence ofawaitor the fact that thereadFileSynccall happens in anasyncfunction does not affect its internal behavior.While
readFileSyncmay speed up the benchmark, I think it is unsuitable for applications that could be doing other work while waiting for I/O to complete.That cannot be optimal? Maybe in this synthetic example it is, but won’t
readFileSyncjust block the whole program until the entire file is read? Theawaitin this case just gives the illusion of being async as a microtick is introduced after the entire file has been read synchronously…