węzeł.wydajność js z zeromq vs. Python vs. Java

Napisałem prosty test echo request/reply dla zeromq za pomocą node.js, Python i Java. Kod uruchamia pętlę 100k żądań. Platforma to 5-letni MacBook Pro z 2 rdzeniami i 3G pamięci RAM z systemem Snow Leopard.

Węzeł.js jest konsekwentnie o rząd wielkości wolniejszy niż pozostałe dwie platformy.

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

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

Węzeł.js: real 3m19.034s user 2m43.460s sys 0m24.668s

Co ciekawe, w Pythonie i Javie zarówno procesy klienckie, jak i serwerowe używają około połowy procesora. Klient dla node.js używa tylko około pełnego procesora, a serwer używa około 30% procesora. Proces Klienta ma również ogromną liczbę błędów strony, co prowadzi mnie do przekonania, że jest to problem z pamięcią. Ponadto, przy 10K żądań węzeł jest tylko 3 razy wolniejszy; zdecydowanie spowalnia bardziej im dłużej działa.

Oto kod klienta (zauważ, że proces.linia exit() również nie działa, dlatego dodałem wewnętrzny timer oprócz użycia polecenia 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);
});

Serwer kod:

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

Dla porównania, kod klienta i serwera Pythona:

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!")

Oraz kod klienta i serwera Javy:

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

Chciałbym polubić node, ale z ogromną różnicą w rozmiarze kodu, prostocie i wydajności, trudno byłoby mi się przekonać w tym momencie.

Czy ktoś widział już takie zachowanie, czy zrobiłem coś głupiego w kodzie?
Author: Scott A, 2011-07-11

6 answers

Używasz wiązania C++ innej firmy. O ile dobrze rozumiem, zwrotnica pomiędzy "js-land" v8 i wiązaniami do v8 napisanymi w "C++ land", jest bardzo droga. Jeśli zauważysz, jakaś popularna baza wiązania dla node są zaimplementowane w całości w JS (chociaż częściowo jestem pewien, ponieważ ludzie nie chcą kompilować rzeczy, ale także dlatego, że ma potencjał, aby być bardzo szybki).

Jeśli dobrze pamiętam, kiedy Ryan Dahl zapisywał Obiekty bufora dla węzła, zauważył, że są one o wiele szybsze, jeśli zaimplementował je głównie w JS, a nie w C++. W końcu napisał to, co musiał w C++, a Wszystko inne zrobił w pure javascript.

Zgaduję, że część problemu z wydajnością ma związek z tym konkretnym modułem, który jest wiązaniem c++.

Ocenianie wydajności węzła na podstawie zewnętrznego modułu nie jest dobrym medium do określania jego szybkości lub jakości. Zrobiłbyś o wiele lepiej, aby porównywać natywny interfejs TCP node.

 17
Author: chjj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-26 12:08:03

"Czy możesz spróbować symulować logikę na przykładzie Pythona(np. wysyłam następną wiadomość dopiero po otrzymaniu poprzedniej)?"- Andrey Sidorov Jul 11 at 6:24

Myślę, że to część tego:

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

Ta wersja nie wykazuje tego samego rosnącego spowolnienia, co poprzednia, prawdopodobnie dlatego, że nie wysyła tyle żądań, ile to możliwe na serwer, a tylko zlicza odpowiedzi, jak poprzednia wersja. Jest około 1,5 razy wolniejszy niż Python / Java w przeciwieństwie do 5-10 razy wolniejszy w poprzednia wersja.

Nadal nie Oszałamiająca pochwała node do tego celu, ale na pewno dużo lepsza niż"fatalna".

 9
Author: Scott A,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-22 15:33:10

To był problem z wiązaniami zeroMQ w node. Nie wiem od kiedy, ale to jest stałe i masz takie same wyniki jak w innych językach.

 9
Author: Dan Milon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-02 00:22:52

Nie znam się na node.js, ale sposób, w jaki go wykonujesz, to rekurencyjne tworzenie nowych funkcji w kółko, nic dziwnego, że wybucha. aby być na równi z Pythonem lub Javą, kod musi być bardziej zgodny z:

    if (counter < maxnum)
    {
       socket.send('Hello');
       processmessages();  // or something similar in node.js if available
    }
 4
Author: madprogrammer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-11 02:52:46

Wszelkie testy wydajności przy użyciu gniazd REQ / REP zostaną Przekrzywione z powodu potknięcia okrągłego i opóźnień wątku. Budzisz cały stos, w dół i w górę, dla każdej wiadomości. Nie jest to zbyt przydatne jako metryka, ponieważ przypadki REQ / REP nigdy nie są wysokiej wydajności(nie mogą być). Istnieją dwa lepsze testy wydajności:

  • wysyłanie wielu wiadomości o różnej wielkości od 1 bajtu do 1K, zobacz ile możesz wysłać w ciągu np. 10 sekund. Zapewnia to podstawową przepustowość. Dzięki temu dowiesz się, jak wydajny jest stos.
  • Zmierz opóźnienie od końca do końca, ale strumienia messsages; tzn. włóż znacznik czasu w każdej wiadomości i zobacz, jakie odchylenie jest na odbiorniku. To mówi, czy stos ma jitter, np. ze względu na usuwanie śmieci.
 2
Author: Pieter Hintjens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-01 11:48:42

Twój Klient kod Pythona blokuje się w pętli. W przykładzie węzła zdarzenia są odbierane asynchronicznie. Jeśli wszystko, co chcesz od swojego klienta, to odbierać dane z zmq, Twój kod w Pythonie będzie bardziej wydajny, ponieważ jest zakodowany jako wyspecjalizowany, jednokierunkowy kucyk. Jeśli chcesz dodać funkcje, takie jak listen to other events that are not using zmq, then you ' ll find it complicated to re-write the Python code to do tego. Dzięki node wystarczy dodać kolejny opiekun imprezy. node nigdy nie będzie bestią wydajności dla prostych przykładów. Jednak, ponieważ twój projekt staje się bardziej skomplikowany z większą liczbą ruchomych elementów, o wiele łatwiej jest dodać funkcje poprawnie do węzła niż zrobić to z vanilla python, który napisałeś. Wolałbym raczej rzucić trochę więcej pieniędzy na sprzęt, zwiększyć czytelność i zmniejszyć mój czas/koszt rozwoju.

 1
Author: matthewaveryusa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-24 16:55:10