Подтвердить что ты не робот

Node.js производительность с zeromq против Python против Java

Я написал простой echo-запрос/ответ для zeromq, используя node.js, Python и Java. Код запускает цикл из 100K запросов. Платформа представляет собой пятиядерный MacBook Pro с двумя ядрами и 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% процессора. Клиентский процесс также имеет огромное количество ошибок страниц, что заставляет меня думать, что это проблема памяти. Кроме того, при запросах 10K node работает только в 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!")

И код клиента и сервера 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();
    }
}

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

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

4b9b3361

Ответ 1

"вы можете попытаться моделировать логику из своего примера на Python (e.i отправить следующее сообщение только после получения предыдущего)?" - Андрей Сидоров 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 раз медленнее в предыдущей версии.

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

Ответ 2

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

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

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

  • Отправка многих сообщений различного размера от 1 байт до 1K, см., сколько вы можете отправить, например. 10 секунд. Это дает вам базовую пропускную способность. Это говорит о том, насколько эффективен стек.
  • Измерение сквозной задержки, но потока сообщений; т.е. вставить временную метку в каждое сообщение и посмотреть, что такое отклонение на приемнике. Это говорит о том, имеет ли стэк дрожание, например. из-за сбора мусора.

Ответ 6

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