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




benchmark is (6)

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.

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?


El código python de su cliente está bloqueando en el bucle. En el ejemplo de nodo, recibe los eventos en el controlador de eventos 'mensaje' de forma asíncrona. Si todo lo que desea de su cliente es recibir datos de zmq, su código de Python será más eficiente porque está codificado como un pony especializado de un solo truco. Si desea agregar funciones como escuchar otros eventos que no están usando zmq, le resultará complicado volver a escribir el código de Python para hacerlo. Con nodo, todo lo que necesitas es agregar otro controlador de eventos. nodo nunca será una bestia de rendimiento para ejemplos simples. Sin embargo, a medida que su proyecto se complica con más piezas en movimiento, es mucho más fácil agregar funciones correctamente al nodo que hacerlo con el pitón de vainilla que ha escrito. Prefiero gastar un poco más de dinero en hardware, aumentar la legibilidad y disminuir mi tiempo / costo de desarrollo.


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.


No estoy muy familiarizado con node.js, pero la forma en que lo ejecutas está creando recursivamente nuevas funciones una y otra vez, no es de extrañar que esté explotando. para estar a la par con python o java, el código debe estar más en la línea de:

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

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.

Analice @staticmethod literalmente proporcionando diferentes perspectivas.

Un método normal de una clase es un método dinámico implícito que toma la instancia como primer argumento.
En contraste, un método estático no toma la instancia como primer argumento, por lo que se llama 'estático' .

Un método estático es, de hecho, una función tan normal como las que están fuera de una definición de clase.
Afortunadamente, está agrupado en la clase solo para estar más cerca de donde se aplica, o puede desplazarse para encontrarlo.





java python node.js zeromq