zeromq 与 Python 与 Java 的 node.js 性能

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6645796/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-16 08:36:32  来源:igfitidea点击:

node.js performance with zeromq vs. Python vs. Java

javapythonnode.jszeromq

提问by Scott A

I've written a simple echo request/reply test for zeromq using node.js, Python, and Java. The code runs a loop of 100K requests. The platform is a 5yo MacBook Pro with 2 cores and 3G of RAM running Snow Leopard.

我已经使用 node.js、Python 和 Java 为 zeromq 编写了一个简单的回显请求/回复测试。该代码运行 10 万个请求的循环。该平台是 5yo MacBook Pro,具有 2 个内核和 3G RAM,运行 Snow Leopard。

node.js is consistently an order of magnitude slower than the other two platforms.

node.js 始终比其他两个平台慢一个数量级。

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

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

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

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

node.js: real 3m19.034s user 2m43.460s sys 0m24.668s

节点.js: real 3m19.034s user 2m43.460s sys 0m24.668s

Interestingly, with Python and Java the client and server processes both use about half of a CPU. The client for node.js uses just about a full CPU and the server uses about 30% of a CPU. The client process also has an enormous number of page faults leading me to believe this is a memory issue. Also, at 10K requests node is only 3 times slower; it definitely slows down more the longer it runs.

有趣的是,使用 Python 和 Java,客户端和服务器进程都使用大约一半的 CPU。node.js 的客户端使用大约一个完整的 CPU,服务器使用大约 30% 的 CPU。客户端进程也有大量的页面错误,让我相信这是一个内存问题。此外,在 10K 请求时,节点仅慢 3 倍;它运行的时间越长,它肯定会越慢。

Here's the client code (note that the process.exit() line doesn't work, either, which is why I included an internal timer in addition to using the time command):

这是客户端代码(注意 process.exit() 行也不起作用,这就是为什么除了使用 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);
});

Server code:

服务器代码:

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

For comparison, the Python client and server code:

为了比较,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!")

And the Java client and server code:

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

I would like to like node, but with the vast difference in code size, simplicity, and performance, I'd have a hard time convincing myself at this point.

我想喜欢 node,但由于代码大小、简单性和性能的巨大差异,我很难在这一点上说服自己。

So, has anyone seen behavior like this before, or did I do something asinine in the code?

那么,以前有没有人看到过这样的行为,或者我在代码中做了什么愚蠢的事情?

采纳答案by Scott A

"can you try to simulate logic from your Python example (e.i send next message only after receiving previous)?" – Andrey Sidorov Jul 11 at 6:24

“你能尝试从你的 Python 示例中模拟逻辑吗(ei 只在收到上一条消息后才发送下一条消息)?” – 安德烈·西多罗夫 7 月 11 日 6:24

I think that's part of it:

我认为这是其中的一部分:

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

This version doesn't exhibit the same increasing slowness as the previous, probably because it's not throwing as many requests as possible at the server and only counting responses like the previous version. It's about 1.5 times as slow as Python/Java as opposed to 5-10 times slower in the previous version.

这个版本没有表现出与前一个相同的缓慢增加,可能是因为它没有向服务器抛出尽可能多的请求,并且只像以前的版本一样计算响应。它的速度大约是 Python/Java 的 1.5 倍,而在之前的版本中则要慢 5-10 倍。

Still not a stunning commendation of node for this purpose, but certainly a lot better than "abysmal".

仍然不是为此目的对节点的惊人赞扬,但肯定比“深渊”要好得多。

回答by chjj

You're using a third party C++ binding. As far as I understand it, the crossover between v8's "js-land" and bindings to v8 written in "c++ land", is very expensive. If you notice, some popular databasebindingsfor node are implemented entirely in JS (although, partly I'm sure, because people don't want to compile things, but also because it has the potential to be very fast).

您正在使用第三方 C++ 绑定。据我了解,v8 的“js-land”与用“c++ land”编写的 v8 绑定之间的交叉非常昂贵。如果您注意到,一些流行的 node数据库绑定完全是在 JS 中实现的(虽然,部分我可以肯定,因为人们不想编译东西,但也因为它有可能非常快)。

If I remember correctly, when Ryan Dahl was writing the Buffer objects for node, he noticed that they were actually a lot faster if he implemented them mostly in JS as opposed to C++. He ended up writing what he had to in C++, and did everything else in pure javascript.

如果我没记错的话,当 Ryan Dahl 为 node 编写 Buffer 对象时,他注意到如果他主要用 JS 而不是 C++ 来实现它们实际上要快得多。他最终用C++编写了他必须写的东西,并用纯 javascript 完成了其他所有工作。

So, I'm guessing part of the performance issue here has to do with that particular module being a c++ binding.

所以,我猜测这里的部分性能问题与作为 C++ 绑定的特定模块有关。

Judging node's performance based on a third party module is not a good medium for determining its speed or quality. You would do a lot better to benchmark node's native TCP interface.

基于第三方模块判断节点的性能不是确定其速度或质量的好媒介。您可以更好地对节点的本机 TCP 接口进行基准测试。

回答by madprogrammer

I'm not all that familiar with node.js, but the way you're executing it is recursively creating new functions over and over again, no wonder it's blowing up. to be on par with python or java, the code needs to be more along the lines of:

我对 node.js 不是很熟悉,但是你执行它的方式是递归地一遍又一遍地创建新函数,难怪它爆炸了。要与 python 或 java 相提并论,代码需要更多地遵循:

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

回答by matthewaveryusa

Your client python code is blocking in the loop. In the node example, you receive the events in the 'message' event handler asynchronously. If all you want from your client is to receive data from zmq then your python code will be more efficient because it is coded as a specialized one-trick pony. If you want to add features like listen to other events that aren't using zmq, then you'll find it complicated to re-write the python code to do so. With node, all you need is to add another event handler. node will never be a performance beast for simple examples. However, as your project gets more complicated with more moving pieces, it's a lot easier to add features correctlyto node than to do so with the vanilla python you've written. I'd much rather toss a little bit more money on hardware, increase readability and decrease my development time/cost.

您的客户端 python 代码在循环中阻塞。在节点示例中,您异步接收“消息”事件处理程序中的事件。如果您只想从客户端接收来自 zmq 的数据,那么您的 Python 代码将更加高效,因为它被编码为专门的一招式小马。如果您想添加诸如侦听其他未使用 zmq 的事件之类的功能,那么您会发现重新编写 python 代码来这样做很复杂。使用 node,您只需要添加另一个事件处理程序。对于简单的例子,节点永远不会成为性能野兽。然而,随着您的项目变得越来越复杂,移动的部分越来越多,正确添加功能会容易得多节点而不是使用您编写的香草 python 这样做。我宁愿在硬件上多花一点钱,提高可读性并减少我的开发时间/成本。

回答by Dan Milon

This was a problem with the zeroMQ bindings of node. I don't know since when, but it is fixed and you get the same results as with the other languages.

这是节点的 zeroMQ 绑定的问题。我不知道从什么时候开始,但它是固定的,你会得到与其他语言相同的结果。

回答by Pieter Hintjens

Any performance testing using REQ/REP sockets is going to be skewed due to round-tripping and thread latencies. You're basically waking up the whole stack, all the way down and up, for each message. It's not very useful as a metric because REQ/REP cases are never high performance (they can't be). There are two better performance tests:

由于往返和线程延迟,任何使用 REQ/REP 套接字的性能测试都会出现偏差。对于每条消息,您基本上都在唤醒整个堆栈,一路上下。它作为一个指标不是很有用,因为 REQ/REP 案例从来都不是高性能的(它们不可能)。有两个更好的性能测试:

  • Sending many messages of various sizes from 1 byte to 1K, see how many you can send in e.g. 10 seconds. This gives you basic throughput. This tells you how efficient the stack is.
  • Measure end-to-end latency but of a stream of messsages; i.e. insert time stamp in each message and see what the deviation is on the receiver. This tells you whether the stack has jitter, e.g. due to garbage collection.
  • 发送许多不同大小的消息,从 1 字节到 1K,看看你能在 10 秒内发送多少。这为您提供了基本的吞吐量。这告诉您堆栈的效率。
  • 测量端到端延迟,但测量消息流;即在每条消息中插入时间戳并查看接收器上的偏差。这会告诉您堆栈是否有抖动,例如由于垃圾收集。