JavaとC / C ++間のプロセス間通信のための最速(低レイテンシ)な方法


100

TCPソケットを介してC / C ++で開発された「サーバー」に接続するJavaアプリがあります。

アプリとサーバーの両方が同じマシン、Solarisボックスで実行されています(ただし、最終的にはLinuxへの移行を検討しています)。交換されるデータの種類は単純なメッセージです(ログイン、ログインACK、クライアントは何かを要求し、サーバーは応答します)。各メッセージの長さは約300バイトです。

現在はソケットを使用しており、すべて問題ありませんが、IPCメソッドを使用してデータを交換する(レイテンシを短縮する)より高速な方法を探しています。

私はネットを調査していて、次のテクノロジーへの参照を思いつきました:

  • 共有メモリ
  • パイプ
  • キュー
  • DMA(Direct Memory Access)と呼ばれるもの

しかし、私はそれぞれのパフォーマンスの適切な分析を見つけることができませんでした。JAVAとC / C ++の両方でそれらを実装する方法も(彼らが互いに通信できるように)できませんでした。

このコンテキストでの各メソッドのパフォーマンスと実現可能性について誰かがコメントできますか?有用な実装情報へのポインタ/リンクはありますか?


編集/更新

ここで得たコメントと回答に続いて、パイプのすぐ上に構築されているように見えるUnixドメインソケットに関する情報を見つけ、TCPスタック全体を節約しました。これはプラットフォーム固有なので、JNIまたはjudsjunixsocketのいずれかでテストする予定です

次に考えられる手順は、パイプの直接実装、次に共有メモリですが、複雑さのレベルがさらに高いという警告を受けています...


ご協力いただきありがとうございます


7
あなたの場合はやり過ぎかもしれませんが、zeromq.orgを
jfs

それは興味深いですが、アイデアは最初に「汎用」(OS提供または言語提供など)メソッドを使用することです。そのため、キューと共有メモリについて言及しました。
バスティエン


マップされたファイルまたはUDPを忘れないでください。

10
UDPはTCPより遅い???
うーん

回答:


103

Corei5 2.8GHzでJavaからのレイテンシをテストしたところ、1バイトの送受信のみで、タスクセットで特定のCPUコアを割り当てることなく、2つのJavaプロセスが生成されました。

TCP         - 25 microseconds
Named pipes - 15 microseconds

ここで、taskset 1 java Srvtaskset 2 java Cliなどのコアマスクを明示的に指定します。

TCP, same cores:                      30 microseconds
TCP, explicit different cores:        22 microseconds
Named pipes, same core:               4-5 microseconds !!!!
Named pipes, taskset different cores: 7-8 microseconds !!!!

そう

TCP overhead is visible
scheduling overhead (or core caches?) is also the culprit

同時に、Thread.sleep(0)(straceが示すように、単一のsched_yield()Linuxカーネルコールが実行される)は0.3マイクロ秒かかります。そのため、シングルコアにスケジュールされた名前付きパイプには、依然として多くのオーバーヘッドがあります。

一部の共有メモリ測定: 2009年9月14日– Solace Systemsは本日、そのユニファイドメッセージングプラットフォームAPIが共有メモリトランスポートを使用して平均700ナノ秒未満のレイテンシを達成できることを発表しました。 http://solacesystems.com/news/fastest-ipc-messaging/

PS-翌日、メモリマップファイルの形式で共有メモリを試しましたが、ビジー待機が許容できる場合は、次のようなコードで1バイトを渡すための待ち時間を0.3マイクロ秒に減らすことができます。

MappedByteBuffer mem =
  new RandomAccessFile("/tmp/mapped.txt", "rw").getChannel()
  .map(FileChannel.MapMode.READ_WRITE, 0, 1);

while(true){
  while(mem.get(0)!=5) Thread.sleep(0); // waiting for client request
  mem.put(0, (byte)10); // sending the reply
}

注:Thread.sleep(0)は、2つのプロセスが互いの変更を確認できるようにするために必要です(別の方法はまだわかりません)。2つのプロセスがタスクセットを使用して同じコアに強制される場合、レイテンシは1.5マイクロ秒になります-これはコンテキストスイッチの遅延です

PPS-0.3マイクロ秒が適切な数値です。次のコードは正確に0.1マイクロ秒かかりますが、プリミティブな文字列連結のみを実行します。

int j=123456789;
String ret = "my-record-key-" + j  + "-in-db";

PPPS-これがあまり話題から外れていないことを願っていますが、最後にThread.sleep(0)を静的な揮発性のint変数のインクリメントに置き換えてみました(そうすると、JVMがCPUキャッシュをフラッシュします)。- 72ナノ秒のレイテンシJava間のプロセス間通信

ただし、同じCPUコアに強制されると、揮発性インクリメントのJVMは互いに制御し合わないため、正確に10ミリ秒のレイテンシが発生します。Linuxタイムクォンタムは5ミリ秒のようです...したがって、スペアコアがある場合にのみ使用してください-それ以外の場合は、sleep(0)の方が安全です。


Andriyに感謝します。非常に情報の調査であり、TCPの測定値とほぼ一致しています。そのため、これは良いリファレンスです。名前付きパイプを調べます。
バスティエン

だから、Thread(Sleep)をvolatile static intのインクリメントに置き換えるのは、プロセスを別のコアに固定できる場合にのみ行うべきですか?また、これができるとは思いませんでしたか?OSが決めると思った?
奇形の

3
LockSupport.parkNanos(1)を試してください。同じことをするはずです。
12

非常に素晴らしい。ただし、TCP pingの方が(5〜7us RTTレイテンシのように)よりよくできます。ここを参照してください:psy-lob-saw.blogspot.com/2012/12/...
Nitsan Wakart

1
JavaでIPCキューをサポートするために、メモリマップファイルを共有メモリとして使用するさらなる調査:psy-lob-saw.blogspot.com/2013/04/lock-free-ipc-queue.html 1秒あたり1億3,000万メッセージを達成。メソッドによるレイテンシの比較研究については、以下の私の回答も参照してください。
Nitsan Wakart 2014年

10

DMAは、ハードウェアデバイスがCPUに割り込むことなく物理RAMにアクセスできる方法です。たとえば、一般的な例は、ディスクからRAMに直接バイトをコピーできるハードディスクコントローラです。そのため、IPCには適用されません。

共有メモリとパイプはどちらも最新のOSで直接サポートされています。そのため、それらは非常に高速です。キューは通常、抽象化です。たとえば、ソケット、パイプ、共有メモリの上に実装されます。これは遅いメカニズムのように見えるかもしれませんが、代わりにそのような抽象作成することです


DMAの場合、ネットワーク全体(特にInfiniBandを使用)に適用されるRDMA(リモートダイレクトメモリアクセスとして)に関連する多くのことを読み取って、これと同じことができるのはなぜですか。私は実際にネットワークなしで同等のものを達成しようとしています(すべてが同じ箱にあるので)。
バスティエン

RDMAは同じ概念です。どちらの側のCPUにも割り込むことなく、ネットワーク全体でバイトをコピーします。それでもプロセスレベルでは動作しません。
MSalters 2010

10

質問は少し前に尋ねられましたが、200 nsの典型的なレイテンシと20 Mメッセージ/秒のスループットをサポートするhttps://github.com/peter-lawrey/Java-Chronicleに興味があるかもしれません。プロセス間で共有されるメモリマップファイルを使用します(データを永続化するため、データを永続化する最速の方法になります)



6

ネイティブアクセスの使用を検討する場合(アプリケーションと「サーバー」の両方が同じマシン上にあるため)、JNAを検討すると、対処するボイラープレートコードが少なくなります。


6

遅れて到着しましたが、Java NIOを使用したpingレイテンシの測定に特化したオープンソースプロジェクトを指摘したかったのです。

このブログ投稿でさらに調査/説明しました。結果は次のとおりです(RTT(ナノ単位))。

Implementation, Min,   50%,   90%,   99%,   99.9%, 99.99%,Max
IPC busy-spin,  89,    127,   168,   3326,  6501,  11555, 25131
UDP busy-spin,  4597,  5224,  5391,  5958,  8466,  10918, 18396
TCP busy-spin,  6244,  6784,  7475,  8697,  11070, 16791, 27265
TCP select-now, 8858,  9617,  9845,  12173, 13845, 19417, 26171
TCP block,      10696, 13103, 13299, 14428, 15629, 20373, 32149
TCP select,     13425, 15426, 15743, 18035, 20719, 24793, 37877

これは受け入れられた答えの線に沿っています。System.nanotime()エラー(何も測定しないと推定)は約40ナノ秒で測定されるため、IPCの場合、実際の結果は低くなる可能性があります。楽しい。


2

ネイティブプロセス間通信についてはあまり知りませんが、JNIメカニズムを使用してアクセスできるネイティブコードを使用して通信する必要があると思います。したがって、Javaから、他のプロセスと通信するネイティブ関数を呼び出します。



0

接続を再利用できるように、ソケットを開いたままにすることを検討しましたか?


ソケットは開いたままです。接続は、アプリケーションの実行中ずっと有効です(約7時間)。メッセージは多かれ少なかれ継続的に交換されます(毎秒5〜10程度としましょう)。現在のレイテンシは約200マイクロ秒であり、目標は1桁または2桁を削ることです。
バスティエン

2ミリ秒のレイテンシ?野心的。JNIを使​​用してインターフェイスできる共有ライブラリにCのものを書き換えることは可能でしょうか?
するThorbjörnRavnアンデルセン

2msは200マイクロ秒であり、200マイクロ秒ではありません。これにより、2msの野心がはるかに少なくなります。
thewhiteambit 2017年

-1

JNIパフォーマンスに関するOracleバグレポート:http : //bugs.java.com/bugdatabase/view_bug.do?bug_id=4096069

JNIは低速のインターフェースであるため、Java TCPソケットはアプリケーション間の通知の最速の方法ですが、それは、ソケットを介してペイロードを送信する必要があるという意味ではありません。LDMAを使用してペイロードを転送しますが、前の質問で指摘したように、メモリマッピングのJavaサポートは理想的ではないため、mmapを実行するためにJNIライブラリを実装する必要があります。


3
JNIが遅いのはなぜですか?Javaの低レベルTCPレイヤーがどのように機能するかを考えてください。Javaバイトコードで書かれていません。(たとえば、これはネイティブホストを通過する必要があります。)したがって、Java TCPソケットはJNIよりも速いという主張を拒否します。(ただし、JNIはIPCではありません。)

4
プリミティブのみを使用する場合、1回のJNI呼び出しで9ナノ秒かかる(Intel i5の場合)。だから、それほど遅くはありません。
Martin Kersten、2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.