Являются ли дочерние процессы узла намного медленнее?

Рассмотрим следующий код, который аппроксимирует число пи:

// filename: pi.js
function approx_pi(from, to) {
    var pi = 0;
    var dx = 0.0000000005;
    for(var x = from; x < to; x += dx) {
        pi += 4 / (1 + x * x);
    }
    return pi * dx;
}

var min = Infinity;
var max = -1;
var avg = 0;

for(var itr = 0; itr < 10; ++itr) {
    var start = process.hrtime();
    var pi = approx_pi(0, 1);
    var diff = process.hrtime(start);

    var duration = diff[0] + diff[1] * 1e-9;

    min = (duration <= min) ? duration : min;
    max = (duration >= max) ? duration : max;
    avg += duration;
}

avg /= 10;

min = min.toFixed(3);
max = max.toFixed(3);
avg = avg.toFixed(3);

console.log("Time: min = %ss, max = %ss , avg = %ss", min, max, avg);

Также рассмотрите этот код, который запускает файл выше в n дочерних процессах, в то время как n задается через аргументы командной строки:

//filename: children.js
var cp = require('child_process');

var n = parseInt(process.argv[2]);

for(var k = 0; k < n; ++k) {
    cp.fork('pi.js');
}

При запуске node pi.js вывод:

Time: min = 19.113s, max = 22.220s , avg = 21.152s

Аналогичный результат получается при запуске node children.js 1 (что ожидается)

Time: min = 17.323s, max = 21.465s , avg = 19.979s

Вещи начинают становиться странными (по крайней мере, по моим собственным ожиданиям), когда разветвляется более одного ребенка. Вот результат после запуска node children.js 2 :

Time: min = 29.824s, max = 41.050s , avg = 35.136s
Time: min = 30.036s, max = 40.791s , avg = 35.246s

каждому ребенку потребовалось как минимум на 14 секунд больше, чем в одиночных версиях. Еще хуже было при запуске 4 дочерних элементов с node children.js 4 , каждому из них требовалось примерно 37 секунд, чтобы закончить:

Time: min = 55.878s, max = 68.047s , avg = 58.845s
Time: min = 52.760s, max = 69.168s , avg = 58.880s
Time: min = 57.151s, max = 69.113s , avg = 58.956s
Time: min = 50.790s, max = 70.344s , avg = 59.546s

Эти тесты проводились на машине с процессором Core i5 2410M с тактовой частотой 2,30 ГГц (4 ядра, видимые для ОС) и 4 ГБ ОЗУ, с обоими: Windows 7, работающей на узле v4.2.3, и Ubuntu14.04, работающей на узле v5.4.1. Каждая ОС показала, что каждый из дочерних процессов работал на своем собственном ядре и у каждого был одинаковый объем выделенной памяти (около 9 МБ).

Согласно документации узла здесь: "Каждый процесс имеет свою собственную память с их собственные экземпляры V8". Таким образом, можно было бы ожидать, что каждый ребенок должен работать так же хорошо, как отдельный процесс.

Являются ли мои ожидания ошибочными, и это ожидаемое поведение или что происходит?


person m0stafa    schedule 25.04.2016    source источник
comment
Ваш тест должен действительно повторяться не менее 10 раз, а затем сообщать минимальное, максимальное и среднее время. Это дает V8 время для компиляции вашего кода (первый запуск всегда будет самым медленным). Кроме того, Date.now() не очень точен (поэтому он будет переоценивать время).   -  person JDB still remembers Monica    schedule 25.04.2016
comment
@JDB Я усреднил более 10 итераций, как вы указали, и использовал process.hrtime вместо Date.now. Хотя данные не сильно отличались.   -  person m0stafa    schedule 25.04.2016


Ответы (1)


Вы запускаете 4 процесса параллельно на процессоре с 2 ядра (4 потока), а затем измеряется фактическое время, а не время процессора. Конечно, каждый процесс займет больше времени... вы не можете запустить бесконечное количество процессов на ограниченном количестве ядер и ожидать, что время выполнения будет таким же, как при запуске одного процесса на нескольких ядрах. (Если кто-нибудь когда-нибудь поймет, как это сделать, то кибербезопасность будет под угрозой.)

Вы можете ожидать, что 2 процесса, работающих на 2 ядрах, будут работать одновременно с одним процессом (который должен работать на одном ядре), но реальность часто не совсем совпадает с теорией. Вполне возможно, что ваши 2 процесса на самом деле делят время на одном ядре. Нет простого способа гарантировать, что каждый процесс получит собственное ядро, поскольку такие детали почти всегда абстрагируются от вашего контроля.

person JDB still remembers Monica    schedule 25.04.2016
comment
Я отключил гиперпоточность на своей машине, чтобы два ядра, видимые ОС, на самом деле были двумя ядрами, а не двумя потоками на одном ядре. Затем я запускал node children.js 1 и node chidren.js 2, разница на этот раз была около +10 с (чуть меньше, чем с гиперпоточностью, которая была +14 с), но все равно много! Я попробовал ту же задачу с С++, используя mpi, и разница составила около +3,3 с! Вы думаете, что дополнительные +6,7 с в js просто из-за скорости C++ по сравнению с js? - person m0stafa; 25.04.2016
comment
Вы по-прежнему не можете гарантировать, что каждый процесс выполняется на отдельном ядре. Вот и решающий вопрос... они работают на отдельных ядрах (действительно параллельно) или они используют одно ядро ​​(снижая скорость каждого)? Тот факт, что один дочерний процесс выполняется в то же время, что и его родитель, предполагает, что дочерние процессы не медленнее. Разница в скорости с C++ не кажется мне актуальной... Я предполагаю, что C++ в любом случае работает быстрее, поскольку он скомпилирован в собственный байтовый код, в то время как Node использует JIT-компилятор, поэтому процент быстрее будет лучшим сравнением, чем сырые числа. - person JDB still remembers Monica; 26.04.2016
comment
Ну, судя по диспетчеру задач ОС, они работают на отдельных ядрах. Когда я убиваю один из процессов, процент использования одного ядра падает, а другого остается на уровне 100%. Но дело в том, что когда работает один дочерний, он занимает все ядро ​​под себя, процессы os и родительский процесс делят другое ядро. Когда два дочерних процесса работают на обоих ядрах, они не могут иметь их все для себя, они разделяют их с другими процессами ОС. Это объясняет, почему они занимают больше времени, но меня беспокоит величина разницы! - person m0stafa; 26.04.2016
comment
Я думаю, что это хорошая тема для исследования, но я не думаю, что ваши тесты действительно доказывают, что есть разница или в чем разница. C++ — чрезвычайно оптимизированный и компактный код по сравнению с Node, поэтому разработчики игр по-прежнему предпочитают его. Я думаю, вам нужно провести дополнительное тестирование, прежде чем объявить, что дочерние процессы Node на самом деле занимают больше времени. - person JDB still remembers Monica; 26.04.2016
comment
Полагаю, что так. Я думаю, что эти тесты нужно запускать в более контролируемой среде, чтобы иметь возможность делать какие-либо выводы из них. Спасибо за вашу помощь! - person m0stafa; 26.04.2016