📜 ⬆️ ⬇️

Comparing taking from the cache of the same file using fs.readFileSync and fs.readFile (and reading multiple files)

After reading the article Episode 8: Interview with Ryan Dahl, Creator of Node.js and comments on the translation , I decided to test the effectiveness of blocking and non-blocking reading of the file in Node.js, under the cut of the table and graphics.


UPD: Under the cut is a bad benchmark. As correctly indicated in the comments, essentially taking the same file from the cache is compared using fs.readFileSync and fs.readFile.


UPD2: The article has been edited, the benchmark has been corrected, the results have been added.


The blocking operation (fs.readFileSync is one of these) assumes that the execution of the entire application will be suspended until non-JS-related direct operations are performed.


Non-blocking operations allow you to perform non-JS operations asynchronously in parallel threads (for example, fs.readFile).


More about blocking vs non-blocking here .


Although Node.js runs in one thread, using child_process or cluster, you can distribute the execution of the code into several threads.


Tests were conducted with parallel and sequential reading of the cached file (large and small), as well as reading of uncached files.


All tests took place on the same computer, with one HDD, and immeno:


OS: Ubuntu 16.04
Node.js version: 8.4.0
Processor: AMD Phenom (tm) 9750 Quad-Core Processor
Physical cores: 4
HDD: 2TB 7200rpm 64MB
File system type: ext4
file.txt size: 3.3 kB
bigFile.txt size: 6.5 MB

Results for cached file.


When reading 3.3 kB file 10,000 times


SymbolNameops / secPercents
ALoop readFileSync7.4100%
BPromise chain readFileSync4.4760%
CPromise chain readFile1.0915%
DPromise.all readFileSync4.5862%
EPromise.all readFile1.6923%
FMultithread loop readFileSync20.05271%
GMultithread promise.all readFile4.9867%

When reading 3.3 kB file 100 times


SymbolNameops / secPercents
ALoop readFileSync747100%
BPromise chain readFileSync64186%
CPromise chain readFile120sixteen%
DPromise.all readFileSync66489%
EPromise.all readFile23832%
FMultithread loop readFileSync1050140%
GMultithread promise.all readFile37250%

When reading 6.5 MB file 100 times


SymbolNameops / secPercents
ALoop readFileSync0.6383%
BPromise chain readFileSync0.6687%
CPromise chain readFile0.6180%
DPromise.all readFileSync0.6687%
EPromise.all readFile0.76100%
FMultithread loop readFileSync0.83109%
GMultithread promise.all readFile0.81107%

CPU load when reading 3.3 kB file 10,000 times
file.txt, reading 10,000 times


CPU usage when reading 6.5 MB file 100 times
bigFile.txt, reading 100 times


As you can see, fs.readFileSync is always executed in one thread on one core. fs.readFile uses several threads in its work, but the kernels are not loaded at full capacity. For a small file, fs.readFileSync is faster than fs.readFile, and only when reading a large file when a node is running in one stream, fs.readFile runs faster than fs.readFileSync.


Therefore, reading small files is best done using fs.readFileSync, and large files using fs.readFile (how big the file should be depends on the computer and software).


For some tasks, fs.readFileSync may be preferable for reading large files. For example, with a long reading and processing of multiple files. In this case, the load between the cores must be distributed using child_process. Roughly speaking, run the node itself, and not operations in multiple threads.


UPD2
Below is the data obtained for reading a lot of un-cached files of the same size (3.3kB).


When reading 1000 files


SymbolNameops / secPercents
ALoop readFileSync8.4774%
BPromise chain readFileSync6.2855%
CPromise chain readFile5.4948%
DPromise.all readFileSync8.0670%
EPromise.all readFile11.05100%
FMultithread loop readFileSync3.7132%
GMultithread promise.all readFile5.1144%

When reading 100 files


SymbolNameops / secPercents
ALoop readFileSync79.1985%
BPromise chain readFileSync50.1754%
CPromise chain readFile48.4652%
DPromise.all readFileSync54.758%
EPromise.all readFile92.87100%
FMultithread loop readFileSync80.4686%
GMultithread promise.all readFile92.1999%

CPU usage when reading unzipped files is small, about 20%. Results vary ± 30%.
The results show that using non-blocking fs.readFile is more profitable.


An example of a file reading situation.


Suppose we have a web server on a node in a single T1 thread. Two requests come at the server at the same time (P1 and P2) for reading and processing small files (one per request). When using fs.readFileSync, the sequence of execution of the code in the T1 stream will look like this:


P1 -> P2


When using fs.readFile, the sequence of code execution in the T1 stream will look like this:


P1-1 -> P2-1 -> P1-2 -> P2-2


Where P1-1, P2-1 is a delegation of reading to another thread, P1-2, P2-2 is getting reading results and processing data.


')

Source: https://habr.com/ru/post/337746/


All Articles