web-dev-qa-db-fra.com

Transmettre un grand tableau au processus enfant du nœud

Je souhaite effectuer un travail complexe nécessitant beaucoup de ressources en processeur sur un large éventail. Idéalement, j'aimerais transmettre ceci au processus enfant. 

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());
});

Mais quand je le fais, le noeud donne l'erreur:

faire apparaître E2BIG

Je suis tombé sur cet article

Il semble donc que la transmission des données au processus enfant semble être la voie à suivre. Mon code est maintenant:

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());
});

Et ensuite dans getStatistics.js:

console.log('im inside child');

process.stdin.on('data', function(data) {
  console.log('data is ', data);
  process.exit(0);
});

Cependant, le rappel dans process.stdin.on n'est pas atteint. Comment puis-je recevoir un flux dans mon script enfant?

MODIFIER

J'ai dû abandonner l'approche tampon. Maintenant, j'envoie le tableau sous forme de message:

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

child.send({ 
  dataAsNumbers: dataAsNumbers
});

Mais cela ne fonctionne que lorsque la longueur de dataAsNumbers est inférieure à environ 20 000, sinon elle expire.

23
Mark

Avec une telle quantité de données, je chercherais à utiliser une mémoire partagée plutôt que de copier les données dans le processus enfant (ce qui se produit lorsque vous utilisez un canal ou que vous passez des messages). Cela économisera de la mémoire, prendra moins de temps processeur pour le processus parent et ne risquera pas d’atteindre une limite.

shm-typed-array est un module très simple qui semble adapté à votre application. Exemple:

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);

child.js

"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);
});

Notez que nous n’envoyons qu’une petite "clé" du processus parent au processus enfant via IPC, et non l’ensemble des données. Ainsi, nous économisons une tonne de mémoire et de temps.

Bien entendu, vous pouvez remplacer 'Float64Array' (par exemple, une double) par tout type de tableau typé requis par votre application. Notez que cette bibliothèque en particulier ne traite que des tableaux typés à une dimension; mais cela ne devrait être qu'un obstacle mineur.

11
rvighne

Moi aussi j'ai pu reproduire le retard que vous avez connu, mais peut-être pas aussi grave que vous. J'ai utilisé ce qui suit

// 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,
})

Et 

// getStats.js
process.on('message', function (data) {
  console.log('data is ', data)
  process.exit(0)
})

noeud main.js 2.72s utilisateur 0.45s système 103% cpu 3.045 total

Je génère 100 000 éléments composés de 100 nombres pour simuler vos données. Assurez-vous d'utiliser l'événement message sur process. Mais peut-être que vos enfants sont plus complexes et pourraient être la raison de l'échec, cela dépend aussi du délai d'attente que vous avez défini pour votre requête.


Si vous souhaitez obtenir de meilleurs résultats, vous pouvez diviser vos données en plusieurs parties qui seront envoyées au processus enfant et reconstruites pour former le tableau initial.


De plus, une possibilité serait d'utiliser une bibliothèque tierce ou un protocole, même si c'est un peu plus de travail. Vous pourriez jeter un œil à messenger.js ou même à une file d’attente AMQP qui pourrait vous permettre de communiquer entre les deux processus avec un pool et une garantie du message reçu par le sous-processus. Il existe quelques implémentations de nœuds, comme amqp.node , mais cela demanderait quand même un peu de travail d’installation et de configuration.

1
Balthazar

Utilisez un cache en mémoire comme https://github.com/ptarjan/node-cache , et laissez le processus parent stocker le contenu du tableau avec une clé, le processus enfant récupérant le contenu via cette clé.

0
DhruvPathak

Vous pouvez envisager d'utiliser des tuyaux de système d'exploitation vous trouverez ici un résumé en tant qu'entrée de votre application enfant de nœud. 

Je sais que ce n’est pas exactement ce que vous demandez, mais vous pouvez utiliser le module cluster (inclus dans le noeud). De cette façon, vous pouvez obtenir autant d'instances que le nombre de cœurs de votre machine pour accélérer le traitement. De plus, envisagez d'utiliser des flux si vous n'avez pas besoin de disposer de toutes les données avant de commencer le traitement. Si les données à traiter sont trop volumineuses, je les stockerais dans un fichier afin que vous puissiez les ré-initialiser en cas d'erreur au cours du processus. Voici un exemple de mise en cluster.

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)
    }
}

Plus d'infos envoi de messages par les ouvriers question 8534462 .

0
JuanGG

Pourquoi voulez-vous créer un sous-processus? L'envoi de données entre sous-processus risque de coûter plus cher en temps processeur et en temps réel que vous ne gagnerez en rendant le traitement effectué dans le même processus.

Au lieu de cela, je suggérerais que, pour un codage extrêmement efficace, vous envisagiez d'effectuer vos calculs de statistiques dans un thread de travail exécuté dans la même mémoire que le processus principal de nodejs.

Vous pouvez utiliser le NAN pour écrire du code C++ que vous pouvez poster sur un thread de travail, puis demander à ce thread de poster le résultat et un événement dans votre boucle d'événements nodejs.

L'avantage de ceci est que vous n'avez pas besoin de temps supplémentaire pour envoyer les données à un processus différent, mais l'inconvénient est que vous allez écrire un peu de code C++ pour l'action threadée, mais que l'extension NAN devrait en prendre soin. de la tâche difficile pour vous.

0
Soren