使用zeromq与Python与Java的node.js性能

我已经使用node.js,Python和Java为zeromq写了一个简单的echo请求/回复testing。 代码运行一个100K请求的循环。 该平台是一个5yo的MacBook Pro,2个内核和运行Snow Leopard的3G内存。

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中,客户端和服务器进程都使用大约一半的CPU。 node.js的客户端使用了大约一个完整的CPU,而服务器使用了大约30%的CPU。 客户端进程也有大量的页面错误导致我相信这是一个内存问题。 另外,在10K请求节点只有3倍慢; 它的运行时间肯定会减慢。

这是客户端代码(请注意,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); }); 

服务器代码:

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

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

那么,有没有人看过这样的行为,还是我在代码中做了一些事情?

您正在使用第三方C ++绑定。 据我了解,v8的“js-land”与v8的“c ++ land”绑定的交叉是非常昂贵的。 如果你注意到,一些stream行的节点数据库 绑定完全是用JS来实现的(尽pipe我相信部分是因为人们不想编译东西,但也因为它有可能非常快)。

如果我没有记错的话,当Ryan Dahl写节点的Buffer对象的时候,他发现如果他实现的主要是JS,而不是C ++,那么他们实际上要快得多。 他最终用C ++编写了自己的代码 ,并且用纯JavaScript编写了所有其他代码。

所以,我猜这个性能问题的一部分与这个特定的模块是c ++绑定有关。

基于第三方模块来判断节点的性能不是确定其速度或质量的好媒介。 基准节点的本地TCP接口可以做得更好。

“你可以尝试模拟Python示例中的逻辑(只有在接收到之前发送的下一条消息)?” – 安德烈Sidorov 7月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); }); 

这个版本没有像前面那样performance出相同的增长缓慢,可能是因为它没有在服务器上抛出尽可能多的请求,而只是计算类似以前版本的响应。 这是Python / Java的1.5倍左右,而不是以前版本的5-10倍。

对于这个目的,仍然不是一个惊人的节点表彰,但肯定比“糟糕”更好。

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

我并不是所有人都熟悉node.js,但是执行它的方式是recursion地创build新的函数,难怪它会被炸毁。 要与python或java相提并论,代码需要更多地沿着以下几点:

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

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

  • 发送多个不同大小的消息,从1字节到1K,看看你可以发送多less个消息,例如10秒。 这给你基本的吞吐量。 这告诉你堆栈有多高效。
  • 衡量端到端的延迟,但是一连串的消息; 即在每条消息中插入时间标记,并查看接收方的偏差。 这告诉你堆栈是否有抖动,例如由于垃圾收集。

你的客户端Python代码在循环中被阻塞。 在节点示例中,asynchronous收到“消息”事件处理程序中的事件。 如果您希望从您的客户端获得来自zmq的数据,那么您的Python代码将更有效率,因为它被编码为专门的一把手小马。 如果要添加诸如侦听不使用zmq的其他事件的function,那么重新编写python代码会很复杂。 使用节点,您只需添加另一个事件处理程序。 对于简单的例子,节点永远不会是一只性能猛兽。 但是,随着项目变得越来越复杂,向节点添加function要比使用已编写的vanilla python更容易。 我宁愿多花点钱在硬件上,增加可读性,减less我的开发时间/成本。