29

node.js、Python、およびJavaを使用して、zeromqの簡単なエコー要求/応答テストを作成しました。コードは100Kリクエストのループを実行します。プラットフォームは、SnowLeopardを実行する2コアと3GのRAMを備えた5yoMacBookProです。

node.jsは、他の2つのプラットフォームよりも一貫して1桁遅くなっています。

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を使用し、サーバーはCPUの約30%を使用します。クライアントプロセスにも膨大な数のページフォールトがあり、これがメモリの問題であると私は信じています。また、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();
    }
}

ノードが欲しいのですが、コードサイズ、シンプルさ、パフォーマンスに大きな違いがあるため、現時点では納得できません。

それで、誰かが以前にこのような振る舞いを見たことがありますか、それとも私はコードで何か不吉なことをしましたか?

4

6 に答える 6

17

サードパーティのC++バインディングを使用しています。私が理解している限り、v8の「js-land」と「c ++ land」で記述されたv8へのバインディングの間のクロスオーバーは、非常に高価です。お気づきの方もいらっしゃると思いますが、ノード用の一般的なデータベース バインディングの一部は完全にJSで実装されています(ただし、コンパイルしたくないという理由もありますが、非常に高速になる可能性があるためです)。

私の記憶が正しければ、Ryan DahlがノードのBufferオブジェクトを書いていたとき、C ++ではなくJSで実装した方が、実際にははるかに高速であることに気づきました。彼は結局C++でやらなければならないことを書き、それ以外はすべて純粋なjavascriptで行いました。

したがって、ここでのパフォーマンスの問題の一部は、その特定のモジュールがC++バインディングであることに関係していると思います。

サードパーティのモジュールに基づいてノードのパフォーマンスを判断することは、その速度や品質を判断するための適切な媒体ではありません。ノードのネイティブTCPインターフェースのベンチマークを行う方がはるかに優れています。

于 2011-08-26T12:08:03.997 に答える
9

「Pythonの例からロジックをシミュレートしてみてください(eiは前のメッセージを受信した後にのみ次のメッセージを送信します)?」–アンドレイシドロフ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);
});

このバージョンでは、以前のバージョンと同じように速度が低下することはありません。おそらく、サーバーで可能な限り多くのリクエストをスローせず、以前のバージョンのように応答のみをカウントしているためです。以前のバージョンでは5〜10倍遅いのに対し、Python/Javaの約1.5倍遅いです。

それでも、この目的のためのノードの見事な表彰ではありませんが、確かに「異常」よりもはるかに優れています。

于 2011-07-11T21:52:17.263 に答える
9

これは、ノードのzeroMQバインディングの問題でした。いつからかはわかりませんが、修正されており、他の言語と同じ結果が得られます。

于 2012-07-02T00:22:52.503 に答える
4

私はnode.jsにそれほど精通していませんが、それを実行する方法は、新しい関数を何度も再帰的に作成しているので、爆発しているのも不思議ではありません。PythonやJavaと同等であるためには、コードは次の行に沿っている必要があります。

    if (counter < maxnum)
    {
       socket.send('Hello');
       processmessages();  // or something similar in node.js if available
    }
于 2012-01-11T02:52:46.600 に答える
3

REQ / REPソケットを使用したパフォーマンステストは、ラウンドトリップとスレッドの待ち時間のために歪められます。基本的に、メッセージごとに、スタック全体を上下に目覚めさせます。REQ / REPのケースは決して高性能ではないため、メトリックとしてはあまり役立ちません(そうではありません)。2つの優れたパフォーマンステストがあります。

  • 1バイトから1Kまでのさまざまなサイズのメッセージを多数送信します。たとえば、10秒で送信できるメッセージの数を確認します。これにより、基本的なスループットが得られます。これにより、スタックの効率がわかります。
  • エンドツーエンドの遅延を測定しますが、メッセージのストリームを測定します。つまり、各メッセージにタイムスタンプを挿入し、受信者の偏差を確認します。これにより、ガベージコレクションなどが原因でスタックにジッターがあるかどうかがわかります。
于 2013-03-01T11:48:42.280 に答える
1

クライアントのPythonコードがループでブロックされています。ノードの例では、「message」イベントハンドラーでイベントを非同期的に受信します。クライアントに必要なのがzmqからデータを受信することだけである場合、Pythonコードは特殊なワントリックポニーとしてコード化されているため、より効率的になります。zmqを使用していない他のイベントをリッスンするなどの機能を追加する場合は、Pythonコードを書き直すのが複雑になります。ノードを使用する場合、必要なのは別のイベントハンドラーを追加することだけです。簡単な例では、ノードがパフォーマンスの獣になることはありません。ただし、動く部分が増えるとプロジェクトが複雑になるため、機能を正しく追加する方がはるかに簡単です。あなたが書いたバニラパイソンでそうするよりもノードに。ハードウェアにもう少しお金をかけ、読みやすさを向上させ、開発時間/コストを削減したいと思います。

于 2012-06-24T16:55:10.560 に答える