Java和C / C ++之间最快的(低延迟)进程间通信方法


100

我有一个Java应用程序,通过TCP套接字连接到用C / C ++开发的“服务器”。

应用程序和服务器都在同一台计算机上运行,​​这是一个Solaris机器(但我们最终正在考虑迁移到Linux)。交换的数据类型是简单的消息(登录,登录ACK,然后客户端要求某些内容,服务器答复)。每个消息大约300字节长。

当前,我们正在使用套接字,并且一切都很好,但是我正在寻找使用IPC方法交换数据(更快的延迟)的更快方法。

我一直在研究网络,并提出了以下技术的参考:

  • 共享内存
  • 管道
  • s列
  • 以及所谓的DMA(直接内存访问)

但是我找不到对它们各自性能的正确分析,也找不到如何在JAVA和C / C ++中实现它们(以便它们可以相互通信)的方法,但也许我可以想象如何做。

在这种情况下,任何人都可以评论每种方法的性能和可行性吗?任何指向有用的实现信息的指针/链接?


编辑/更新

在我到达这里的评论和答案之后,我找到了有关Unix Domain Sockets的信息,该信息似乎是通过管道构建的,将为我节省整个TCP堆栈。它是特定于平台的,因此我计划使用JNI或judsjunixsocket对其进行测试。

下一个可能的步骤是直接实现管道,然后实现共享内存,尽管我已经被警告过额外的复杂性...


谢谢你的帮助


7
在您的情况下可能矫kill过正,
jfs

有趣的是,但是我们的想法是首先使用“通用”(如OS提供或语言提供的)方法,这就是我提到队列和共享内存的原因。
巴斯蒂安


不要忘记映射的文件或只是UDP。

10
UDP比TCP慢???嗯...请证明
Boppity Bop

Answers:


103

刚刚在我的Corei5 2.8GHz上测试了Java的延迟,仅发送/接收了单字节,仅产生了2个Java进程,而没有为任务集分配特定的CPU内核:

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-第二天以内存映射文件的形式尝试了共享内存,如果可以接受繁忙的等待,我们可以将使用以下代码传递单个字节的延迟降低到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-希望这不是太多的话题,但是最后我尝试用增加静态volatile int变量(JVM这样做时刷新CPU缓存)替换Thread.sleep(0)并获得-记录!- 72纳秒的延迟Java到Java进程通信

但是,当强制使用相同的CPU内核时,易失性递增的JVM永远不会相互控制,从而产生恰好10毫秒的延迟-Linux时间间隔似乎是5ms ...因此,只有在有备用内核时才应使用-否则sleep(0)更安全。


感谢Andriy,这是一项非常有信息的研究,它或多或少地与我对TCP的测量相匹配,所以这是一个很好的参考。我想我将研究命名管道。
巴斯蒂安

因此,仅当您可以将进程固定到不同的内核时,才应使用增加volatile static int来替换Thread(Sleep)。另外,我没有意识到您可以做到这一点?我以为操作系统决定了?
mezamorphic 2012年

3
尝试使用LockSupport.parkNanos(1),应该做同样的事情。
2012年

非常好。不过,您可以为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每秒实现135M消息。另请参阅以下我的答案,以通过方法比较研究延迟。
Nitsan Wakart 2014年

10

DMA是一种方法,硬件设备可以在不中断CPU的情况下访问物理RAM。例如,一个常见的示例是硬盘控制器,它可以将字节直接从磁盘复制到RAM。因此,它不适用于IPC。

现代操作系统直接支持共享内存和管道。因此,它们非常快。队列通常是抽象的,例如在套接字,管道和/或共享内存的顶部实现。这可能看起来像一个较慢的机制,但另一种方法就是创造这样的抽象。


对于DMA,为什么然后我可以阅读许多与RDMA有关的内容(如远程直接内存访问),这些内容将适用于整个网络(尤其是InfiniBand),并且可以执行相同的操作。我实际上正在尝试在没有网络的情况下实现等效功能(因为所有功能都在同一个盒子上)。
巴斯蒂安

RDMA的概念相同:在网络上复制字节而不会中断任何一侧的CPU。它仍然无法在流程级别上运行。
MSalters 2010年



6

如果您曾经考虑使用本机访问(因为您的应用程序和“服务器”都在同一台机器上),请考虑使用JNA,它具有更少的样板代码供您处理。


6

迟到了,但想指出一个开源项目,致力于使用Java NIO测量ping延迟。

在此博客文章中进一步探讨/解释。结果是(RTT单位为nanos):

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毫秒的延迟?雄心勃勃。将C-stuff重写为可以使用JNI接口的共享库是否可行?
托尔比约恩Ravn的安徒生

2ms是2000微秒,而不是200微秒。这使2ms的野心性大大降低。
thewhiteambit

-1

关于JNI性能的Oracle错误报告:http : //bugs.java.com/bugdatabase/view_bug.do? bug_id= 4096069

JNI是一个慢速接口,因此Java TCP套接字是在应用程序之间进行通知的最快方法,但这并不意味着您必须通过套接字发送有效负载。使用LDMA传输有效负载,但是正如前面的问题所指出的那样,Java对内存映射的支持并不理想,因此您将希望实现一个JNI库来运行mmap。


3
为什么JNI慢?考虑一下Java中低层TCP层是如何工作的,它不是用Java字节码编写的!(例如,这必须通过本机主机进行。)因此,我拒绝Java Java套接字比JNI更快的主张。(但是,JNI不是IPC。)

4
如果仅使用原语,则单个JNI调用将花费9ns(在Intel i5上)。所以它并不慢。
Martin Kersten 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.