than - Rendimiento de node.js con zeromq vs. Python vs. Java



nodejs vs python performance (4)

He escrito una prueba simple de solicitud / respuesta de eco para zeromq usando node.js, Python y Java El código ejecuta un bucle de 100K solicitudes. La plataforma es una MacBook Pro de 5 años con 2 núcleos y 3G de RAM que ejecuta Snow Leopard.

node.js es consistentemente un orden de magnitud más lento que las otras dos plataformas.

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

Curiosamente, con Python y Java, el cliente y los procesos del servidor usan aproximadamente la mitad de una CPU. El cliente para node.js usa casi una CPU completa y el servidor usa aproximadamente el 30% de una CPU. El proceso del cliente también tiene una enorme cantidad de errores de página que me llevan a creer que se trata de un problema de memoria. Además, a 10K, el nodo es solo 3 veces más lento; definitivamente ralentiza más cuanto más tiempo se ejecuta.

Aquí está el código del cliente (tenga en cuenta que la línea process.exit () tampoco funciona, razón por la cual incluí un temporizador interno además de usar el comando de tiempo):

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

Código del servidor:

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

Para comparación, el código de cliente y servidor 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!")

Y el código de cliente y servidor 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();
    }
}

Me gustaría un nodo, pero con la gran diferencia en el tamaño del código, la simplicidad y el rendimiento, me costaría convencerme a mí mismo en este punto.

Entonces, ¿alguien ha visto un comportamiento como este antes, o hice algo en el código?

https://code.i-harness.com


"¿puedes intentar simular la lógica de tu ejemplo de Python (ei enviar el siguiente mensaje solo después de recibir el anterior)?" - Andrey Sidorov 11 de julio a las 6:24

Creo que eso es parte de ello:

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

Esta versión no muestra la misma lentitud creciente que la anterior, probablemente porque no está lanzando tantas solicitudes como sea posible en el servidor y solo cuenta las respuestas como la versión anterior. Es aproximadamente 1.5 veces más lento que Python / Java en comparación con 5-10 veces más lento en la versión anterior.

Aún no es una recomendación impresionante de nodo para este propósito, pero ciertamente mucho mejor que "abismal".


Cualquier prueba de rendimiento con sockets REQ / REP será sesgada debido a los disparos redondos y las latencias de los hilos. Básicamente, estás despertando a toda la pila, hacia abajo y hacia arriba, para cada mensaje. No es muy útil como métrica porque los casos REQ / REP nunca son de alto rendimiento (no pueden serlo). Hay dos mejores pruebas de rendimiento:

  • Enviando muchos mensajes de varios tamaños desde 1 byte hasta 1K, vea cuántos puede enviar, por ejemplo, 10 segundos. Esto le da un rendimiento básico. Esto te dice qué tan eficiente es la pila.
  • Mida la latencia de extremo a extremo pero de un flujo de mensajes; es decir, inserte la marca de tiempo en cada mensaje y vea cuál es la desviación en el receptor. Esto le indica si la pila tiene fluctuaciones, por ejemplo, debido a la recolección de basura.

Estás utilizando un enlace de C ++ de terceros. Hasta donde lo entiendo, el cruce entre "js-land" de v8 y los enlaces a v8 escritos en "c ++ land", es muy costoso. Si se da cuenta, algunos bindings database populares para el nodo se implementan completamente en JS (aunque, en parte, estoy seguro, porque la gente no quiere compilar cosas, sino también porque tiene el potencial de ser muy rápido).

Si recuerdo correctamente, cuando Ryan Dahl estaba escribiendo los objetos de Buffer para nodo, notó que en realidad eran mucho más rápidos si los implementaba principalmente en JS en lugar de en C ++. Terminó escribiendo lo que tenía que hacer en C++ , e hizo todo lo demás en javascript puro .

Por lo tanto, supongo que parte del problema de rendimiento aquí tiene que ver con que ese módulo en particular sea un enlace c ++.

Juzgar el rendimiento del nodo basado en un módulo de terceros no es un buen medio para determinar su velocidad o calidad. Lo haría mucho mejor para evaluar la interfaz TCP nativa del nodo.


Este fue un problema con los enlaces zeroMQ del nodo. No lo sé desde cuándo, pero está arreglado y obtienes los mismos resultados que con los otros idiomas.





zeromq