web-dev-qa-db-fra.com

Node.js Performance avec Zeromq vs. Python vs. Java==

J'ai écrit un test de demande/réponse d'écho simple pour Zeromq à l'aide de Node.js, Python et Java. Le code exécute une boucle de demandes de 100 km. La plate-forme est un 5YO MacBook Pro avec 2 cœurs et 3G de RAM Running Snow Leopard.

nœud.js est systématiquement un ordre de grandeur plus lent que les deux autres plates-formes.

Java: real 0m18.823s user 0m2.735s sys 0m6.042s

Python: real 0m18.600s user 0m2.656s sys 0m5.857s

nœud.js: real 3m19.034s user 2m43.460s sys 0m24.668s

Intéressant, avec Python et Java Le client et le serveur traitent à la fois environ la moitié d'une CPU. Le client pour nœud.js utilise à peu près un CPU complet et Le serveur utilise environ 30% d'une CPU. Le processus client a également un nombre énorme de défauts de page qui me conduira à croire qu'il s'agit d'un problème de mémoire. En outre, au nœud de demandes de 10k n'est que 3 fois plus lent; il ralentit définitivement plus longtemps. il court.

Voici le code client (notez que la ligne de processus.exit () ne fonctionne pas non plus, c'est pourquoi j'ai inclus une minuterie interne en plus de l'utilisation de la commande horaire):

var zeromq = require("zeromq");

var counter = 0;
var startTime = new Date();

var maxnum = 10000;

var socket = zeromq.createSocket('req');

socket.connect("tcp://127.0.0.1:5502");
console.log("Connected to port 5502.");

function moo()
{
    process.nextTick(function(){
        socket.send('Hello');
        if (counter < maxnum)
        {
            moo();
        }
    });
}

moo();

socket.on('message',
          function(data)
          {
              if (counter % 1000 == 0)
              {
                  console.log(data.toString('utf8'), counter);
              }

              if (counter >= maxnum)
              {
                  var endTime = new Date();
                  console.log("Time: ", startTime, endTime);
                  console.log("ms  : ", endTime - startTime);
                  process.exit(0);
              }

              //console.log("Received: " + data);
              counter += 1;

          }
);

socket.on('error', function(error) {
  console.log("Error: "+error);
});

Code serveur:

var zeromq = require("zeromq");

var socket = zeromq.createSocket('rep');

socket.bind("tcp://127.0.0.1:5502",
            function(err)
            {
                if (err) throw err;
                console.log("Bound to port 5502.");

                socket.on('message', function(envelope, blank, data)
                          {
                              socket.send(envelope.toString('utf8') + " Blancmange!");
                          });

                socket.on('error', function(err) {
                    console.log("Error: "+err);
                });
            }
);

Pour la comparaison, le Python Client et code serveur:

import zmq

context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.connect("tcp://127.0.0.1:5502")

for counter in range(0, 100001):
    socket.send("Hello")
    message = socket.recv()

    if counter % 1000 == 0:
        print message, counter



import zmq

context = zmq.Context()
socket = context.socket(zmq.REP)

socket.bind("tcp://127.0.0.1:5502")
print "Bound to port 5502."

while True:
    message = socket.recv()
    socket.send(message + " Blancmange!")

Et le Java client et code serveur:

package com.moo.test;

import org.zeromq.ZMQ;
import org.zeromq.ZMQ.Context;
import org.zeromq.ZMQ.Socket;

public class TestClient
{
    public static void main (String[] args)
    {
        Context context = ZMQ.context(1);

        Socket requester = context.socket(ZMQ.REQ);
        requester.connect("tcp://127.0.0.1:5502");

        System.out.println("Connected to port 5502.");

        for (int counter = 0; counter < 100001; counter++)
        {
            if (!requester.send("Hello".getBytes(), 0))
            {
                throw new RuntimeException("Error on send.");
            }

            byte[] reply = requester.recv(0);
            if (reply == null)
            {
                throw new RuntimeException("Error on receive.");
            }

            if (counter % 1000 == 0)
            {
                String replyValue = new String(reply);
                System.out.println((new String(reply)) + " " + counter);
            }
        }

        requester.close();
        context.term();
    }
}

package com.moo.test;

import org.zeromq.ZMQ;
import org.zeromq.ZMQ.Context;
import org.zeromq.ZMQ.Socket;

public class TestServer
{
    public static void main (String[] args) {
        Context context = ZMQ.context(1);

        Socket socket  = context.socket(ZMQ.REP);
        socket.bind("tcp://127.0.0.1:5502");

        System.out.println("Bound to port 5502.");

        while (!Thread.currentThread().isInterrupted())
        {
            byte[] request = socket.recv(0);
            if (request == null)
            {
                throw new RuntimeException("Error on receive.");
            }

            if (!socket.send(" Blancmange!".getBytes(), 0))
            {
                throw new RuntimeException("Error on send.");
            }
        }

        socket.close();
        context.term();
    }
}

Je voudrais aimer le nœud, mais avec la vaste différence de taille de code, de simplicité et de performance, j'aurais du mal à me convaincre à ce stade.

Alors, quelqu'un a-t-il déjà vu un comportement comme celui-ci avant, ou ai-je fait quelque chose d'asinine dans le code?

29
Scott A

"Pouvez-vous essayer de simuler la logique à partir de votre Python Exemple (e.i Envoyer un message suivant seulement après avoir reçu précédent)?" - Andrey Sidorov juil 11 à 6:24

Je pense que cela fait partie:

var zeromq = require("zeromq");

var counter = 0;
var startTime = new Date();

var maxnum = 100000;

var socket = zeromq.createSocket('req');

socket.connect("tcp://127.0.0.1:5502");
console.log("Connected to port 5502.");

socket.send('Hello');

socket.on('message',
          function(data)
          {
              if (counter % 1000 == 0)
              {
                  console.log(data.toString('utf8'), counter);
              }

              if (counter >= maxnum)
              {
                  var endTime = new Date();
                  console.log("Time: ", startTime, endTime);
                  console.log("ms  : ", endTime - startTime);
                  socket.close(); // or the process.exit(0) won't work.
                  process.exit(0);
              }

              //console.log("Received: " + data);
              counter += 1;

          socket.send('Hello');
          }
     );

socket.on('error', function(error) {
    console.log("Error: "+error);
});

Cette version n'expose pas la même lenteur croissante que la précédente, probablement parce que cela ne lance pas autant de demandes que possible sur le serveur et ne comptant que des réponses telles que la version précédente. Il est environ 1,5 fois plus lent que Python/Java, au lieu de 5 à 10 fois plus lents dans la version précédente.

Toujours pas une félicitation époustouflée de nœud à cette fin, mais certainement beaucoup mieux que "abyssal".

9
Scott A

Vous utilisez une troisième liaison C++. Autant que je sache, le crossover entre "JS-Land" et ses liaisons de V8 à V8 écrit dans "Terre C++", est très coûteux. Si vous remarquez, une base de données populaire Les liaisons pour le nœud sont implémentés entièrement dans JS (bien que, en partie je suis sûr, parce que les gens ne veulent pas compiler des choses, mais aussi parce que cela a le potentiel d'être très rapide).

Si je me souviens bien, lorsque Ryan Dahl écrivait les objets de tampon pour le nœud, il remarqua qu'ils étaient effectivement beaucoup plus rapides s'il les impliquait principalement dans JS par opposition à C++. Il a fini par écrire ce qu'il avait à entrer C++ , et a fait tout autre dans pure javascript .

Donc, je suppose que la question de la question de la performance concerne ici que ce module particulier est une liaison C++.

Les performances du nœud de Noeud basées sur un module tiers ne constituent pas un bon support pour déterminer sa vitesse ou sa qualité. Vous feriez beaucoup mieux au nœud de référence Native TCP.

17
chjj

C'était un problème avec les liaisons Zeromq du nœud. Je ne sais pas depuis quand, mais il est réparé et vous obtenez les mêmes résultats que dans les autres langues.

9
Dan Milon

Je ne suis pas tout ce qui connaît avec noeud.js, mais la façon dont vous l'exécutissez, il crée de nouvelles fonctions encore et encore, il n'est pas étonnant que cela explose. Pour être au pair avec python ou Java, le code doit être plus important de:

    if (counter < maxnum)
    {
       socket.send('Hello');
       processmessages();  // or something similar in node.js if available
    }
4
madprogrammer

Tous les tests de performance utilisant des prises REQ/REP vont être asymétriques en raison de la cueillissement et des latences de filetage. Vous êtes essentiellement réveillé toute la pile, jusqu'au bout et en haut, pour chaque message. Ce n'est pas très utile en tant que métrique parce que les cas REQ/REP ne sont jamais élevés (ils ne peuvent pas être). Il existe deux meilleurs tests de performance:

  • Envoi de nombreux messages de différentes tailles à partir de 1 km à 1k, voir combien vous pouvez envoyer par exemple par ex. 10 secondes. Cela vous donne un débit de base. Cela vous indique à quel point la pile est efficace.
  • Mesurer la latence de bout en bout, mais d'un flux de messages; I.e. Insérez l'horodatage dans chaque message et voyez quelle est la déviation sur le récepteur. Cela vous dit si la pile a la gigue, par exemple en raison de la collecte des ordures.
2
Pieter Hintjens

Votre client python code bloque dans la boucle. Dans l'exemple de noeud, vous recevez les événements dans le gestionnaire d'événements "Message" de manière asynchrone. Si tout ce que vous voulez de votre client, vous devez recevoir des données de ZMQ. Ensuite, votre python code sera plus efficace car il est codé en tant que poney spécialisé à un truc. Si vous souhaitez ajouter des fonctionnalités comme Écouter d'autres événements qui n'utilisent pas ZMQ, puis ll Trouvez-le compliqué pour ré-écrire le python code pour le faire. Avec le nœud, tout ce dont vous avez besoin est d'ajouter un autre gestionnaire d'événement. Node ne sera jamais une bête de performance pour des exemples simples. cependant, Lorsque votre projet devient plus compliqué avec plus de pièces mobiles, il est beaucoup plus facile d'ajouter des fonctions correctement au noeud que de le faire avec la vanille python vous avez écrit. Je préférerais beaucoup lancer un peu plus d'argent sur le matériel, augmenter la lisibilité et réduire mon temps/coût de développement.

1
matthewaveryusa