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

2022-08-31 09:57:26

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

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

目前我们正在使用Sockets,一切都很好,但是我正在寻找一种更快的方法来交换数据(更低的延迟),使用IPC方法。

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

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

但是我找不到对它们各自性能的适当分析,也找不到如何在JAVA和C / C++中实现它们(以便它们可以相互通信),除了我可以想象如何做的管道。

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


编辑/更新

在我在这里得到的评论和答案之后,我找到了有关Unix域套接字的信息,它似乎只是在管道上构建的,并且可以为我节省整个TCP堆栈。它是特定于平台的,所以我计划用JNI或judsjunixsocket进行测试

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


感谢您的帮助


答案 1

刚刚在我的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 微秒 - 这是上下文切换延迟

P.P.S - 0.3微秒是一个不错的数字!以下代码正好需要 0.1 微秒,同时仅执行基元字符串串联:

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

P.P.P.S - 希望这不会太偏离主题,但最终我尝试将Thread.sleep(0)替换为递增静态易失性int变量(JVM在这样做时碰巧刷新CPU缓存)并获得 - 记录!- 72纳秒延迟的Java到Java进程通信

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


答案 2

这个问题是前段时间问过的,但你可能对 https://github.com/peter-lawrey/Java-Chronicle 感兴趣,它支持200 ns的典型延迟和20 M消息/秒的吞吐量。它使用进程之间共享的内存映射文件(它还保留数据,使其成为保存数据的最快方法)


推荐