Производительность ввода-вывода в рабочих потоках Node.js

Вот пример с рабочим потоком, который занимает ~ 600 мс на локальной машине для синхронного ввода-вывода:

const fs = require('fs');
const { isMainThread, Worker, parentPort, workerData } = require('worker_threads');

const filename = './foo.txt';

if (isMainThread) {
    (async () => {
        console.time('!');

        await new Promise((resolve, reject) => {
            const worker = new Worker(__filename, { workerData: filename });

            worker.on('message', resolve);
            worker.on('error', reject);
            worker.on('exit', (code) => {
                if (code !== 0)
                    reject(new Error(`Worker stopped with exit code ${code}`));
            });
        });

        console.timeEnd('!');   
    })().catch(console.error);
} else {
    for (let i = 0; i < 100; i++)
        fs.readFileSync(workerData);

    parentPort.postMessage('ok');
}

Тот же пример с одним потоком занимает ~ 2 с для асинхронного ввода-вывода:

const fs = require('fs');

const filename = './foo.txt';

console.time('worker');

(function read(i) {
    if (i < 100) {
        fs.readFile(filename, () => read(++i));
        return;
    }

    console.timeEnd('worker');  
})(0);

Очевидно, что синхронная блокировка здесь более эффективна.

Node.js ссылка на рабочий поток гласит:

Воркеры полезны для выполнения операций JavaScript, интенсивно использующих ЦП; не используйте их для ввода-вывода, так как встроенные в Node.js механизмы для асинхронного выполнения операций уже обрабатывают его более эффективно, чем рабочие потоки.

Каковы основания для этого утверждения?

В чем разница между основными и рабочими потоками в отношении ввода-вывода?

Разве цель работника не в том, чтобы не ограничиваться неблокирующими асинхронными операциями?

Каковы обстоятельства, при которых производительность ввода-вывода может быть менее эффективной в рабочих потоках?


person Estus Flask    schedule 04.10.2018    source источник
comment
вздох этот вопрос задавался так много раз в самых разных формах. асинхронный ввод-вывод != синхронный ввод-вывод в другом потоке. только файловый ввод-вывод и поиск DNS выполняются во встроенном пуле потоков libuv, и вам не следует возиться с написанием собственного механизма ввода-вывода.   -  person David Haim    schedule 04.10.2018
comment
Я понимаю, что вы имеете в виду под асинхронным вводом-выводом!= синхронным вводом-выводом в другом потоке, но вопрос относится к рабочим потокам. Зачем такое заявление, если оно кажется вводящим в заблуждение разработчика? Если ссылка сравнивает асинхронный ввод-вывод в основном потоке с асинхронным вводом-выводом в рабочем потоке (сравнение кажется яблоками против яблок, но вряд ли это справедливо, потому что рабочий процесс не должен быть неблокирующим), то некоторое объяснение добро пожаловать.   -  person Estus Flask    schedule 04.10.2018
comment
Если вы согласны с утверждением асинхронный ввод-вывод != синхронный ввод-вывод в другом потоке, то в чем вопрос? постановка задачи ввода-вывода в очередь другому потоку не делает ее неблокирующей.   -  person David Haim    schedule 04.10.2018
comment
Вопрос в том, в чем смысл этого утверждения и при каких обстоятельствах может быть более эффективно выполнять ввод-вывод в основном, а не в рабочем потоке, как предполагает ссылка.   -  person Estus Flask    schedule 04.10.2018
comment
Может под асинхронный поток выделяется меньше процентов процессора, а нагрузка такая же.. И основной поток получает фору.. Один медведь против 1 волка, кто победит.. Но один медведь против целой стаи это другое история..   -  person boateng    schedule 04.10.2018
comment
Опять же, где противоречие? в руководстве говорится, что не используйте рабочие потоки в качестве рабочих операций ввода-вывода, и ваш пример усиливает это утверждение. ни в коем случае вы не должны использовать пул потоков как способ распараллелить свои задачи ввода-вывода.   -  person David Haim    schedule 04.10.2018
comment
Это твой настоящий код? Он должен выдать «TypeError: недостаточно аргументов для MessagePort.postMessage». Кроме того, насколько велик foo.txt?   -  person Ry-♦    schedule 04.10.2018
comment
Потоки полезны для процессов с интенсивным использованием ЦП, поскольку накладные расходы на создание потока компенсируются за счет эффективного использования ЦП. Процессы, связанные с вводом-выводом, более эффективны в основном потоке, потому что накладные расходы на создание дополнительного потока сводятся на нет вызовом ввода-вывода, блокирующим этот поток, т. Е. Node.js’s built-in mechanisms for performing operations asynchronously ..(is already)... more efficient, поэтому накладные расходы на создание потока являются пустой тратой.   -  person Liam    schedule 04.10.2018
comment
@DavidHaim Как это делает утверждение более сильным? Работнику требуется меньше времени. Я бы не стал использовать блокировку операции в основном потоке, но для рабочего это не проблема. Этот пример доказывает, что оператор не использовать их для ввода/вывода неверен. Если вы предполагаете, что для других операций ввода-вывода это может быть другим, пожалуйста, рассмотрите возможность ответа на вопрос; Я считаю, что это применимо к широкой аудитории.   -  person Estus Flask    schedule 04.10.2018
comment
@Ry- Спасибо, исправил. Он достаточно большой, несколько мегабайт.   -  person Estus Flask    schedule 04.10.2018
comment
Несмотря на то, что основной поток более эффективен, люди, скорее всего, будут использовать отдельный поток для ввода-вывода из-за возможного зависания из-за низкой пропускной способности или блокировок.   -  person boateng    schedule 04.10.2018
comment
readFile, вероятно, делает что-то не так или предполагает, что вы будете использовать его для файлов меньшего размера. С 40 МБ foo.txt я получаю 10 секунд для fs.readFile, 2,3 секунды для fs.readFileSync в воркере, 4,5 секунды для ручного эквивалента fs.readFile с использованием fs.open + fs.read и 3,0 секунды при настройке размера буфера. (fs.createReadStream сопоставимо.)   -  person Ry-♦    schedule 04.10.2018