- PVSM.RU - https://www.pvsm.ru -

Сравнение взятия из кэша одного и того же файла с помощью fs.readFileSync и fs.readFile (и чтения множества файлов)

Прочитав статью Episode 8: Interview with Ryan Dahl, Creator of Node.js [1] и комментарии к переводу [2], я решил протестировать эффективность блокирующей и неблокирующей операции чтения файла в Node.js, под катом таблицы и графики.

UPD: Под катом некоректний бенчмарк. Как правильно указали в комментариях, по сути сравнивается взятие из кэша одного и того же файла с помощью fs.readFileSync и fs.readFile.

UPD2: Статья отредактирована, бенчмарк исправлен, результаты добавлены.

Блокирующая операция (fs.readFileSync одна из таких) предполагает что исполнение всего приложения будет приостановлено пока не связанные с JS на прямую операции не будут выполнены.

Неблокирующие опрации позволяют выполнять не связанные с JS операции асинхронно в параллельных потоках (например, fs.readFile).

Больше об blocking vs non-blocking здесь [3].

Хоть Node.js исполняется в одном потоке, с помощью child_process или cluster можно распределить исполнение кода на несколько потоков.

Были проведены тесты [4] с параллельным и последовательным чтением закэшированого файла (большого и маленького), а также чтения незакешированых файлов.

Все тесты проходили на одном компьютере, с одним HDD, а иммено:

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

Результаты для закэшированого файла.

При чтении 3.3 kB файла 10 000 раз

Symbol Name ops/sec Percents
A Loop readFileSync 7.4 100%
B Promise chain readFileSync 4.47 60%
C Promise chain readFile 1.09 15%
D Promise.all readFileSync 4.58 62%
E Promise.all readFile 1.69 23%
F Multithread loop readFileSync 20.05 271%
G Multithread promise.all readFile 4.98 67%

При чтении 3.3 kB файла 100 раз

Symbol Name ops/sec Percents
A Loop readFileSync 747 100%
B Promise chain readFileSync 641 86%
C Promise chain readFile 120 16%
D Promise.all readFileSync 664 89%
E Promise.all readFile 238 32%
F Multithread loop readFileSync 1050 140%
G Multithread promise.all readFile 372 50%

При чтении 6.5 MB файла 100 раз

Symbol Name ops/sec Percents
A Loop readFileSync 0.63 83%
B Promise chain readFileSync 0.66 87%
C Promise chain readFile 0.61 80%
D Promise.all readFileSync 0.66 87%
E Promise.all readFile 0.76 100%
F Multithread loop readFileSync 0.83 109%
G Multithread promise.all readFile 0.81 107%

Загрузка процессора при чтении 3.3 kB файла 10 000 раз
file.txt, reading 10000 times [5]

Загрузка процессора при чтении 6.5 MB файла 100 раз
bigFile.txt, reading 100 times [6]

Как видим fs.readFileSync всегда исполняется в одном потоке на одном ядре. fs.readFile в своей работе использует несколько потоков, но ядра при этом загружены не на полную мощность. Для небольшого файла fs.readFileSync работает быстрее чем fs.readFile, и только при чтении большого файла при ноде запущенной в одном потоке fs.readFile исполняется быстрее чем fs.readFileSync.

Следовательно, чтение небольших файлов лучше проводить с помощью fs.readFileSync, а больших файлов с помощью fs.readFile (насколько файл должен быть большой зависит от компьютера и софта).

Для некоторых задач fs.readFileSync может быть предпочтительнее и для чтения больших файлов. Например при длительном чтении и обработке множества файлов. При этом нагрузку между ядрами надо распределять с помощью child_process. Грубо говоря, запустить саму ноду, а не операции в несколько потоков.

UPD2
Ниже данные полученные для при чтении множества незакешированых файлов одинакового размера (3,3kB).

При чтении по 1000 файлов

Symbol Name ops/sec Percents
A Loop readFileSync 8.47 74%
B Promise chain readFileSync 6.28 55%
C Promise chain readFile 5.49 48%
D Promise.all readFileSync 8.06 70%
E Promise.all readFile 11.05 100%
F Multithread loop readFileSync 3.71 32%
G Multithread promise.all readFile 5.11 44%

При чтении по 100 файлов

Symbol Name ops/sec Percents
A Loop readFileSync 79.19 85%
B Promise chain readFileSync 50.17 54%
C Promise chain readFile 48.46 52%
D Promise.all readFileSync 54.7 58%
E Promise.all readFile 92.87 100%
F Multithread loop readFileSync 80.46 86%
G Multithread promise.all readFile 92.19 99%

Загрузка процессора при чтении незакешированых файлов небольшая, порядка 20%. Результаты варьируются ± 30%.
По результатам видно что использовать неблокирующий fs.readFile выгоднее.

Пример ситуации чтения файла.

Допустим у нас крутится веб сервер на ноде в одном потоке T1. На сервер одновременно приходит два запроса (P1 и P2) чтения и обработки небольших файлов (по одному на запрос). При использовании fs.readFileSync последовательность исполнения кода в потоке Т1 будет выглядеть так:

P1 -> P2

При использовании fs.readFile последовательность исполнения кода в потоке Т1 будет выглядеть так:

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

Где P1-1, P2-1 — делегирование чтения в другой поток, P1-2, P2-2 — получение результатов чтения и обработка данных.

Автор: Shvab

Источник [7]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/263745

Ссылки в тексте:

[1] Episode 8: Interview with Ryan Dahl, Creator of Node.js: https://www.mappingthejourney.com/single-post/2017/08/31/Episode-8-Interview-with-Ryan-Dahl-Creator-of-Nodejs

[2] переводу: https://habrahabr.ru/post/337098/

[3] здесь: https://nodejs.org/en/docs/guides/blocking-vs-non-blocking/

[4] тесты: https://github.com/shvabuk/read-file-benchmark

[5] Image: https://raw.githubusercontent.com/shvabuk/read-file-benchmark/master/img/file-10000.png

[6] Image: https://raw.githubusercontent.com/shvabuk/read-file-benchmark/master/img/bigFile-100.png

[7] Источник: https://habrahabr.ru/post/337746/