Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is fs.readFileSync() faster than await fsPromises.readFile()?

Tags:

node.js

fs

Here is the test code (in an express environment just because that's what I happen to be messing around with):

const fs = require('fs-extra');
const fsPromises = fs.promises;
const express = require('express');
const app = express();

const speedtest = async function (req, res, next) {
    const useFsPromises = (req.params.promises == 'true');
    const jsonFileName = './json/big-file.json';
    const hrstart = process.hrtime();
    if (useFsPromises) {
        await fsPromises.readFile(jsonFileName);
    } else {
        fs.readFileSync(jsonFileName);
    }
    res.send(`time taken to read: ${process.hrtime(hrstart)[1]/1000000} ms`);
};

app.get('/speedtest/:promises', speedtest);

The big-file.json file is around 16 MB. Using node 12.18.4.

Typical results (varies quite a bit around these values, but the following are "typical"):

https://dev.mydomain.com/speedtest/false
time taken to read: 3.948152 ms

https://dev.mydomain.com/speedtest/true
time taken to read: 61.865763 ms

UPDATE to include two more variants... plain fs.readFile() and also a promisified version of this:

const fs = require('fs-extra');
const fsPromises = fs.promises;
const util = require('util');
const readFile = util.promisify(fs.readFile);
const express = require('express');
const app = express();

const speedtest = async function (req, res, next) {
    const type = req.params.type;
    const jsonFileName = './json/big-file.json';
    const hrstart = process.hrtime();
    if (type == 'readFileFsPromises') {
        await fsPromises.readFile(jsonFileName);
    } else if (type == 'readFileSync') {
        fs.readFileSync(jsonFileName);
    } else if (type == 'readFileAsync') {
        return fs.readFile(jsonFileName, function (err, jsondata) {
            res.send(`time taken to read: ${process.hrtime(hrstart)[1]/1000000} ms`);
        });
    } else if (type == 'readFilePromisified') {
        await readFile(jsonFileName);
    }
    res.send(`time taken to read: ${process.hrtime(hrstart)[1]/1000000} ms`);
};

app.get('/speedtest/:type', speedtest);

I am finding that the fsPromises.readFile() is the slowest, while the others are much faster and all roughly the same in terms of reading time. I should add that in a different example (which I can't fully verify so I'm not sure what was going on) the time difference was vastly bigger than reported here. Seems to me at present that fsPromises.readFile() should simply be avoided because there are other async/promise options.

like image 945
drmrbrewer Avatar asked Sep 19 '20 17:09

drmrbrewer


People also ask

What is the difference between FS readFile and FS readFileSync?

In fs. readFile() method, we can read a file in a non-blocking asynchronous way, but in fs. readFileSync() method, we can read files in a synchronous way, i.e. we are telling node. js to block other parallel process and do the current file reading process.

What does the readFile () method require?

readFile() method is used to read the file. This method read the entire file into buffer. To load the fs module, we use require() method. It Asynchronously reads the entire contents of a file.

What does readFile mean?

Reads data from the specified file or input/output (I/O) device. Reads occur at the position specified by the file pointer if supported by the device. This function is designed for both synchronous and asynchronous operations. For a similar function designed solely for asynchronous operation, see ReadFileEx.

Is readFile asynchronous?

readFile() returns a promise to await upon in an async function. The single fulfilled promise provides one Node. js Buffer .


1 Answers

After stepping through each implementation in the debugger (fs.readFileSync and fs.promises.readFile), I can confirm that the synchronous version reads the entire file in one large chunk (the size of the file). Whereas fs.promises.readFile() reads 16,384 bytes at a time in a loop, with an await on each read. This is going to make fs.promises.readFile() go back to the event loop multiple times before it can read the entire file. Besides giving other things a chance to run, it's extra overhead to go back to the event loop every cycle through a for loop. There's also memory management overhead because fs.promises.readFile() allocates a series of Buffer objects and then combines them all at the end, whereas fs.readFileSync() allocates one large Buffer object at the beginning and just reads the entire file into that one Buffer.

So, the synchronous version, which is allowed to hog the entire CPU, is just faster from a pure time to completion point of view (it's significantly less efficient from a CPU cycles used point of view in a multi-user server because it blocks the event loop from doing anything else during the read). The asynchronous version is reading in smaller chunks, probably to avoid blocking the event loop too much so other things can effectively interleave and run while fs.promises.readFile() is doing its thing.

For a project I worked on awhile ago, I wrote my own simple asynchronous version of readFile() that reads the entire file at once and it was significantly faster than the built-in implementation. I was not concerned about event loop blockage in that particular project so I did not investigate if that's an issue.


In addition, fs.readFile() reads the file in 524,288 byte chunks (much larger chunks that fs.promises.readFile()) and does not use await, using just plain callbacks. It is apparently just coded more optimally than the promise implementation. I don't know why they rewrote the function in a slower way for the fs.promises.readFile() implementation. For now, it appears that wrapping fs.readFile() with a promise would be faster.

like image 135
jfriend00 Avatar answered Nov 16 '22 03:11

jfriend00