Производительность node.js с Zeromq против Python против Java - PullRequest
29 голосов
/ 11 июля 2011

Я написал простой тест эхо-запроса / ответа для zeromq, используя node.js, Python и Java.Код запускает цикл из 100 000 запросов.Платформа представляет собой 5-летний MacBook Pro с 2 ядрами и 3G ОЗУ под управлением Snow Leopard.

node.js на порядок медленнее, чем две другие платформы.

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

Интересно, что в Python и Java процессы клиента и сервера используют около половины ЦП.Клиент для node.js использует практически полный ЦП, а сервер использует около 30% ЦП.Клиентский процесс также имеет огромное количество ошибок страниц, что заставляет меня думать, что это проблема памяти.Кроме того, на 10 000 запросов узел работает только в 3 раза медленнее;он определенно замедляется, чем дольше он работает.

Вот код клиента (обратите внимание, что строка process.exit () тоже не работает, поэтому я добавил внутренний таймер в дополнение к использованиюкоманда времени):

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

Код сервера:

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

Для сравнения: клиент и сервер 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!")

И клиент и сервер Javacode:

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

Мне бы хотелось, чтобы мне нравился узел, но с огромной разницей в размере, простоте и производительности кода мне было бы трудно убедить себя в этом.

Итак, кто-нибудь видел такое поведение раньше или я делал что-то глупое в коде?

Ответы [ 6 ]

17 голосов
/ 26 августа 2011

Вы используете стороннюю привязку C ++. Насколько я понимаю, пересечение между "js-land" в v8 и привязками к v8, написанными на "c ++ land", очень дорого. Если вы заметили, некоторые популярные базы данных привязок для узла полностью реализованы в JS (хотя, отчасти я уверен, потому что люди не хотят что-то компилировать, но и потому, что это имеет потенциал быть очень быстрым).

Если я правильно помню, когда Райан Даль писал объекты Buffer для узла, он заметил, что они на самом деле были намного быстрее, если он реализовал их в основном в JS, а не в C ++. В итоге он написал то, что должен был в C ++ , а все остальное сделал в pure javascript .

Итак, я полагаю, что часть проблемы с производительностью связана с тем, что этот конкретный модуль является связыванием c ++.

Оценка производительности узла на основе стороннего модуля не является хорошим средством для определения его скорости или качества. Вы могли бы сделать намного лучше для сравнения собственного TCP-интерфейса узла.

9 голосов
/ 02 июля 2012

Это была проблема с привязками zeroMQ узла. С тех пор я не знаю, но это исправлено, и вы получите те же результаты, что и с другими языками.

9 голосов
/ 12 июля 2011

"Можете ли вы попытаться смоделировать логику из вашего примера Python (например, отправлять следующее сообщение только после получения предыдущего)?"- Андрей Сидоров 11 июля в 6: 24

Я думаю, что это является частью этого:

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

Эта версия не демонстрирует такую ​​же медлительность, как предыдущая, вероятно, потому что она не бросаеткак можно больше запросов на сервере и только подсчет ответов, как в предыдущей версии.Это примерно в 1,5 раза медленнее, чем Python / Java, по сравнению с 5-10 раз медленнее в предыдущей версии.

Все еще не ошеломляющая оценка узла для этой цели, но, конечно, намного лучше, чем "бездонный".

4 голосов
/ 11 января 2012

Я не так уж хорошо знаком с node.js, но то, как вы выполняете его, - это рекурсивное создание новых функций снова и снова, не удивительно, что он взрывается. чтобы быть наравне с Python или Java, код должен быть примерно таким:

    if (counter < maxnum)
    {
       socket.send('Hello');
       processmessages();  // or something similar in node.js if available
    }
2 голосов
/ 01 марта 2013

Любое тестирование производительности с использованием сокетов REQ / REP будет искажено из-за циклических отключений и задержек потоков. По сути, вы просыпаете весь стек, весь путь вниз и вверх, для каждого сообщения. Это не очень полезно в качестве показателя, потому что случаи REQ / REP никогда не бывают высокими (не могут быть). Есть два теста на лучшую производительность:

  • Отправка большого количества сообщений разного размера от 1 байта до 1 КБ, посмотрите, сколько вы можете отправить, например, 10 секунд. Это дает вам базовую пропускную способность. Это говорит вам, насколько эффективен стек.
  • Измерение сквозной задержки, но потока сообщений; То есть вставьте отметку времени в каждое сообщение и посмотрите, какое отклонение получателя. Это говорит вам, есть ли в стеке дрожание, например из-за сбора мусора.
1 голос
/ 24 июня 2012

Ваш клиентский код Python блокируется в цикле. В примере узла вы получаете события в обработчике событий message в асинхронном режиме. Если все, что вы хотите от своего клиента - это получать данные из zmq, тогда ваш код на python будет более эффективным, поскольку он закодирован как специализированный пони с одним приемом. Если вы хотите добавить такие функции, как прослушивание других событий, которые не используют zmq, вам будет сложно переписать код на python для этого. С узлом все, что вам нужно, это добавить еще один обработчик событий. узел никогда не будет зверем производительности для простых примеров. Однако по мере того, как ваш проект усложняется за счет увеличения количества движущихся частей, гораздо проще добавлять в узел функции правильно , чем с помощью написанного вами ванильного питона. Я бы предпочел потратить немного больше денег на аппаратное обеспечение, повысить удобочитаемость и сократить время и затраты на разработку.

...