Javascript 将大型数组传递给节点子进程

Javascript 将大型数组传递给节点子进程,javascript,node.js,mongodb,Javascript,Node.js,Mongodb,我有复杂的CPU密集型工作要在大型阵列上完成。理想情况下,我希望将其传递给子进程 var spawn = require('child_process').spawn; // dataAsNumbers is a large 2D array var child = spawn(process.execPath, ['/child_process_scripts/getStatistics', dataAsNumbers]); child.stdout.on('data', function

我有复杂的CPU密集型工作要在大型阵列上完成。理想情况下,我希望将其传递给子进程

var spawn = require('child_process').spawn;

// dataAsNumbers is a large 2D array
var child = spawn(process.execPath, ['/child_process_scripts/getStatistics', dataAsNumbers]);

child.stdout.on('data', function(data){
  console.log('from child: ', data.toString());
});
但当我这样做时,node会给出错误:

产卵量大

我偶然发现

因此,将数据管道化到子进程似乎是一条可行之路。我的代码是:

var spawn = require('child_process').spawn;

console.log('creating child........................');

var options = { stdio: [null, null, null, 'pipe'] };
var args = [ '/getStatistics' ];
var child = spawn(process.execPath, args, options);

var pipe = child.stdio[3];

pipe.write(Buffer('awesome'));

child.stdout.on('data', function(data){
  console.log('from child: ', data.toString());
});
然后在getStatistics.js中:

console.log('im inside child');

process.stdin.on('data', function(data) {
  console.log('data is ', data);
  process.exit(0);
});
但是,上的
进程.stdin.中的回调未到达。如何在我的子脚本中接收流

编辑

我不得不放弃缓冲区方法。现在我以消息的形式发送数组:

var cp = require('child_process');
var child = cp.fork('/getStatistics.js');

child.send({ 
  dataAsNumbers: dataAsNumbers
});

但这仅在DataAsNumber的长度低于20000时有效,否则它会超时。

对于长流程任务,您可以使用类似于可以对工作人员执行繁重工作流程的方法,这样您可以设置需要多少工作人员,例如我以这种方式执行一些文件处理,如果我需要你创建更多的worker实例,我也有不同的worker用于不同的任务,处理zip文件,生成缩略图等等,这样做的好处是worker可以在任何语言node.js、Java、python上编写,并且可以轻松地集成到你的项目中

// worker-unzip.js
const debug = require('debug')('worker:unzip');
const {series, apply} = require('async');
const gearman = require('gearmanode');
const {mkdirpSync} = require('fs-extra');
const extract = require('extract-zip');

module.exports.unzip = unzip;
module.exports.worker = worker;

function unzip(inputPath, outputDirPath, done) {
  debug('unzipping', inputPath, 'to', outputDirPath);
  mkdirpSync(outputDirPath);
  extract(inputPath, {dir: outputDirPath}, done);
}


/**
 *
 * @param {Job} job
 */
function workerUnzip(job) {
  const {inputPath, outputDirPath} = JSON.parse(job.payload);
  series([
    apply(unzip, inputPath, outputDirPath),
    (done) => job.workComplete(outputDirPath)
  ], (err) => {
    if (err) {
      console.error(err);
      job.reportError();
    }
  });
}

function worker(config) {
  const worker = gearman.worker(config);
  if (config.id) {
    worker.setWorkerId(config.id);
  }

  worker.addFunction('unzip', workerUnzip, {timeout: 10, toStringEncoding: 'ascii'});
  worker.on('error', (err) => console.error(err));

  return worker;
}
一个简单的index.js

const unzip = require('./worker-unzip').worker;

unzip(config); // pass host and port of the Gearman server
我通常用PM2管理工人

与代码的集成非常简单。差不多

//initialize
const gearman = require('gearmanode');

gearman.Client.logger.transports.console.level = 'error';
const client = gearman.client(configGearman); // same host and port
只需将工作添加到传递函数名称的队列中即可

const taskpayload = {inputPath: '/tmp/sample-file.zip', outputDirPath: '/tmp/unzip/sample-file/'}
const job client.submitJob('unzip', JSON.stringify(taskpayload));
job.on('complete', jobCompleteCallback);
job.on('error', jobErrorCallback);

我也能重现你所经历的延迟,但可能没有你那么糟糕。我使用了以下方法

// main.js
const fork = require('child_process').fork

const child = fork('./getStats.js')

const dataAsNumbers = Array(100000).fill(0).map(() =>
  Array(100).fill(0).map(() => Math.round(Math.random() * 100)))

child.send({
  dataAsNumbers: dataAsNumbers,
})

node main.js 2.72s用户0.45s系统103%cpu总计3.045

我正在生成由100个数字组成的100k元素来模拟您的数据,请确保您正在使用
进程上的
消息
事件。但是,可能您的孩子更复杂,并且可能是失败的原因,这也取决于您对查询设置的超时


如果您想获得更好的结果,您可以将数据分块成多个片段,这些片段将被发送到子进程并重新构造以形成初始数组



还有一种可能是使用第三方库或协议,即使这需要更多的工作。您可以查看AMQP队列,甚至类似于AMQP队列,它允许您使用池在两个进程之间进行通信,并保证消息已被子进程确认。它有一些节点实现,例如,但仍然需要一些设置和配置工作。

对于如此大量的数据,我会考虑使用而不是将数据复制到子进程中(这是使用管道或传递消息时发生的情况)。这将节省内存,减少父进程的CPU时间,并且不太可能遇到某些限制

是一个非常简单的模块,似乎适合您的应用程序。例如:

parent.js

"use strict";

const shm = require('shm-typed-array');
const fork = require('child_process').fork;

// Create shared memory
const SIZE = 20000000;
const data = shm.create(SIZE, 'Float64Array');

// Fill with dummy data
Array.prototype.fill.call(data, 1);

// Spawn child, set up communication, and give shared memory
const child = fork("child.js");
child.on('message', sum => {
    console.log(`Got answer: ${sum}`);

    // Demo only; ideally you'd re-use the same child
    child.kill();
});
child.send(data.key);
"use strict";

const shm = require('shm-typed-array');

process.on('message', key => {
    // Get access to shared memory
    const data = shm.get(key, 'Float64Array');

    // Perform processing
    const sum = Array.prototype.reduce.call(data, (a, b) => a + b, 0);

    // Return processed data
    process.send(sum);
});
child.js

"use strict";

const shm = require('shm-typed-array');
const fork = require('child_process').fork;

// Create shared memory
const SIZE = 20000000;
const data = shm.create(SIZE, 'Float64Array');

// Fill with dummy data
Array.prototype.fill.call(data, 1);

// Spawn child, set up communication, and give shared memory
const child = fork("child.js");
child.on('message', sum => {
    console.log(`Got answer: ${sum}`);

    // Demo only; ideally you'd re-use the same child
    child.kill();
});
child.send(data.key);
"use strict";

const shm = require('shm-typed-array');

process.on('message', key => {
    // Get access to shared memory
    const data = shm.get(key, 'Float64Array');

    // Perform processing
    const sum = Array.prototype.reduce.call(data, (a, b) => a + b, 0);

    // Return processed data
    process.send(sum);
});
请注意,我们只是通过IPC从父进程向子进程发送一个小的“密钥”,而不是整个数据。因此,我们节省了大量的内存和时间


当然,您可以将
'Float64Array'
(例如
双精度
)更改为应用程序所需的任何值。请注意,此库尤其只处理一维类型化数组;但是,这只是一个小的障碍。

使用内存中的缓存,并且让父进程用一些密钥存储数组内容,子进程将通过该密钥来重写内容。

您可以考虑使用OS管道作为节点子应用程序的输入。 我知道这并不是您想要的,但您可以使用该模块(包含在node中)。通过这种方式,您可以获得尽可能多的实例,以加快处理速度。此外,如果在开始处理之前不需要拥有所有可用的数据,可以考虑使用流。如果要处理的数据太大,我会将其存储在一个文件中,以便在处理过程中出现任何错误时可以重新初始化。 这里是一个集群的例子

var cluster = require('cluster');
var numCPUs = 4;

if (cluster.isMaster) {
    for (var i = 0; i < numCPUs; i++) {
        var worker = cluster.fork();
        console.log('id', worker.id)
    }
} else {
    doSomeWork()
}

function doSomeWork(){
    for (var i=1; i<10; i++){
        console.log(i)
    }
}
var cluster=require('cluster');
var numpus=4;
if(cluster.isMaster){
对于(变量i=0;i对于(var i=1;i为什么要创建子流程?跨子流程发送数据在cpu和实时性方面的成本可能高于在同一流程中进行处理所节省的成本

相反,我建议,对于超高效编码,您可以考虑在与NoDEJS主进程相同的内存中运行的工作者线程中进行统计计算。

您可以使用C++编写代码,您可以将其发送到工作线程,然后使用该工作线程将结果和事件返回到您的NoDEJS事件循环中。

这样做的好处是,你不需要额外的时间来把数据发送到不同的进程,但是缺点是你会为线程动作写一点C++代码,但是NNA扩展应该为你处理大部分的困难任务。se是一种多线程语言。该项目已完成90%,我现在不会从node更改。有很多文章解释了node的CPU使用率很高。通常,首先启动一个解决核心问题的项目是一个好主意。在多线程语言中,由于线程共享内存,您不需要复制数据。在这种情况下复制数据case会减慢一切。此外,当您将工作委托给libuv时,节点速度很快。如果您计划使用节点的v8部分进行繁重的处理,那么它不会很快。此外,如果出于任何原因,这是实际服务器的一部分,您的事件循环将阻塞,并且I/O将无法满足您的所有请求