104

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

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

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

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

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

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

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


编辑/更新

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

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


感谢您的帮助

4

10 回答 10

106

刚刚在我的 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 内核时,增加 volatile 的 JVM 永远不会相互控制,因此会产生 10 毫秒的延迟 - Linux 时间量似乎是 5 毫秒......所以只有在有备用核心时才应该使用它 -否则 sleep(0) 更安全。

于 2011-06-20T13:55:39.993 回答
10

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

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

于 2010-04-14T08:58:10.923 回答
10

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

于 2012-07-15T06:48:56.743 回答
8

这是一个包含各种 IPC 传输的性能测试的项目:

http://github.com/rigtorp/ipc-bench

于 2010-04-15T04:54:00.253 回答
6

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

于 2010-04-14T07:10:18.050 回答
6

来晚了,但想指出一个开源项目,专门用于使用 Java NIO 测量 ping 延迟。

在这篇博文中进一步探索/解释。结果是(RTT in 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,实际结果可能会更低。享受。

于 2013-07-03T14:08:12.860 回答
2

我对本机进程间通信了解不多,但我猜您需要使用本机代码进行通信,您可以使用 JNI 机制访问这些代码。因此,从 Java 中,您将调用与其他进程对话的本机函数。

于 2010-04-14T06:52:54.263 回答
1

在我以前的公司,我们曾经使用过这个项目http://remotetea.sourceforge.net/,非常容易理解和集成。

于 2010-04-14T06:33:18.067 回答
0

您是否考虑过保持套接字打开,以便可以重用连接?

于 2010-04-15T05:22:44.983 回答
-1

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

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

于 2010-11-30T08:20:03.673 回答