29 votes

Performances de node.js avec zeromq vs Python vs Java

J'ai écrit un test simple de demande/réponse d'écho pour zeromq en utilisant node.js, Python et Java. Le code exécute une boucle de 100K requêtes. La plateforme est un MacBook Pro de 5 ans avec 2 cœurs et 3G de RAM fonctionnant sous Snow Leopard.

node.js est systématiquement plus lent d'un ordre de grandeur que les deux autres plateformes.

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

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

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

Il est intéressant de noter qu'avec Python et Java, les processus client et serveur utilisent tous deux environ la moitié d'une unité centrale. Le client pour node.js utilise à peu près un CPU complet et le serveur utilise environ 30% d'un CPU. Le processus client présente également un nombre énorme de défauts de page, ce qui me laisse penser qu'il s'agit d'un problème de mémoire. De plus, à 10 000 demandes, node n'est que 3 fois plus lent ; il ralentit définitivement plus il s'exécute longtemps.

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

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 du 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 comparaison, le code du client et du serveur Python :

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 code du client et du serveur Java :

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

J'aimerais aimer node, mais avec la grande différence de taille de code, de simplicité et de performance, j'aurais du mal à me convaincre à ce stade.

Quelqu'un a-t-il déjà vu un tel comportement ou ai-je fait quelque chose de stupide dans le code ?

17voto

chjj Points 5676

Vous utilisez une liaison C++ d'un tiers. D'après ce que je comprends, le passage entre le "js-land" de la v8 et les liaisons à la v8 écrites en "c++-land", est très coûteux. Si vous remarquez, certaines applications populaires base de données fixations pour le nœud sont entièrement implémentés en JS (bien que, en partie j'en suis sûr, parce que les gens ne veulent pas compiler les choses, mais aussi parce que cela a le potentiel d'être très rapide).

Si je me souviens bien, lorsque Ryan Dahl a écrit les objets Buffer pour node, il a remarqué qu'ils étaient beaucoup plus rapides s'ils étaient implémentés principalement en JS plutôt qu'en C++. Il a fini par écrire ce qu'il avait à écrire en C++ et a fait tout le reste en javascript pur .

Donc, je suppose qu'une partie du problème de performance est liée au fait que ce module particulier est une liaison c++.

Juger les performances d'un nœud en se basant sur un module tiers n'est pas un bon moyen de déterminer sa vitesse ou sa qualité. Vous feriez beaucoup mieux d'évaluer l'interface TCP native du nœud.

9voto

Scott A Points 4044

"Pouvez-vous essayer de simuler la logique de votre exemple Python (par exemple, envoyer le message suivant seulement après avoir reçu le précédent) ?" - Andrey Sidorov Jul 11 at 6:24

Je pense que c'est une partie du problème :

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 ne présente pas la même lenteur croissante que la précédente, probablement parce qu'elle ne lance pas autant de requêtes que possible au serveur et ne compte que les réponses comme la version précédente. Elle est environ 1,5 fois plus lente que Python/Java, alors qu'elle était 5 à 10 fois plus lente dans la version précédente.

Il ne s'agit toujours pas d'un éloge éblouissant du nœud pour cet objectif, mais c'est certainement beaucoup mieux que "abyssal".

9voto

Dan Milon Points 446

C'était un problème avec les liaisons zeroMQ de node. Je ne sais pas depuis quand, mais il est corrigé et vous obtenez les mêmes résultats qu'avec les autres langues.

4voto

madprogrammer Points 51

Je ne suis pas très familier avec node.js, mais la façon dont vous l'exécutez consiste à créer de nouvelles fonctions de façon récursive encore et encore, pas étonnant qu'il explose. pour être au même niveau que python ou java, le code doit être plus proche de la ligne suivante :

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

2voto

Pieter Hintjens Points 3677

Tout test de performance utilisant des sockets REQ/REP sera faussé en raison du round-tripping et des latences des threads. En fait, vous réveillez toute la pile, de haut en bas et de bas en haut, pour chaque message. Ce n'est pas très utile comme mesure car les cas REQ/REP ne sont jamais très performants (ils ne peuvent pas l'être). Il existe deux meilleurs tests de performance :

  • En envoyant de nombreux messages de différentes tailles, de 1 octet à 1K, voyez combien vous pouvez en envoyer en 10 secondes par exemple. Cela vous donne le débit de base. Cela vous indique l'efficacité de la pile.
  • Mesurez la latence de bout en bout mais d'un flux de messages ; c'est-à-dire insérez un horodateur dans chaque message et voyez quel est l'écart sur le récepteur. Cela vous indique si la pile présente une gigue, par exemple à cause de la collecte des déchets.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X