Java TCP/IP Socket延迟 - 卡在50微秒(用于Java IPC)?

10

我们一直在对应用程序进行分析和性能优化,以尽可能减少延迟。我们的应用程序由3个独立的Java进程组成,所有进程都运行在同一台服务器上,并通过TCP/IP套接字互相传递消息。

我们已经将第一个组件的处理时间缩短到25微秒,但是我们发现写入下一个组件(位于本地主机)的TCP/IP套接字通信时间不变,大约为50微秒。我们还发现另一个异常行为,即接受连接的组件可以更快地写入数据(即<50微秒)。目前,除了套接字通信之外,所有组件的运行时间都小于100微秒。

我不是TCP/IP专家,不知道如何加速传输速度。Unix域套接字会更快吗?内存映射文件呢?还有哪些机制可能是将数据从一个Java进程传递到另一个进程的更快方法?

更新6/21/2011 我们创建了两个基准测试应用程序,一个使用Java,一个使用C++,以更紧密地测试TCP/IP并进行比较。Java应用程序使用NIO(阻塞模式),而C ++使用Boost ASIO tcp库。结果差不多,C ++应用程序比Java快约4微秒(但在其中一个测试中,Java击败了C++)。此外,两个版本都显示出消息时间的很大变异性。

我认为我们都同意基本结论,共享内存实现将是最快的。(尽管我们也想评估Informatica产品,前提是它符合预算。)


3
微秒的国际单位制简写应该是μs,而不是μ(数量和单位之间应该有一个空格)。我已经为您更正了。 - Marcelo Cantos
我并不是专家,但我猜测UDP可能会降低您的延迟,因为它是一种更轻量级的协议。当然,针对UDP编程会更加痛苦,并且如果您的应用程序必须手动实现TCP提供的相同可靠性保证,则可能不会产生任何好处。 - Marcelo Cantos
标准输入/输出/错误(例如,第一个进程启动另外两个进程,并且通信仅在此“主”进程和两个从进程之间进行),这种方式可行吗? - laher
@Marcelo:谢谢!总是很感激有关更好的语法和语法方面的指针! - Sam Goldberg
只是确认一下 - 你已经关闭了Nagle吗? - Paul Cager
显示剩余3条评论
5个回答

4
如果使用本地库通过JNI是一种选择,我会考虑像平常一样实现IPC(搜索IPC、mmap、shm_open等)。 使用JNI会有很多开销,但至少比使用套接字或管道做任何事情需要的完整系统调用少一点。您可能可以通过JNI使用轮询共享内存IPC实现将单向延迟降低到约3微秒。(确保使用-Xcomp JVM选项或调整编译阈值;否则您的前10000个样本将很糟糕。它会产生很大的影响。) 我有点惊讶于TCP套接字写入需要50微秒-大多数操作系统都会在某种程度上优化TCP环回。Solaris使用称为{{link2:TCP Fusion}}的东西做得相当不错。如果已经对环回通信进行了任何优化,通常都是针对TCP的。UDP往往被忽视,因此在这种情况下我不会费心去用它。我也不会费心去使用管道(stdin / stdout或自己的命名管道等),因为它们会更慢。
一般来说,你看到的大部分延迟可能来自信令 - 要么是像套接字中的select()这样的IO选择器等待,要么是等待信号量,或者是等待其他某些东西。如果你想要尽可能低的延迟,你将不得不烧掉一个核心,坐在一个紧密的循环中轮询新数据。
当然,总有商业现成品路线 - 我可以肯定地说,这会迅速解决你的问题 - 但当然它需要花费金钱。为了完全公开披露:我在Informatica公司工作,负责他们的低延迟消息传递软件。(作为一名工程师,我的诚实意见是,这是非常棒的软件 - 绝对值得为这个项目考虑。)

我查看了你的网站并看到了你们的超级消息产品。我发现在CISCO UCS上它显示的延迟小于1微秒。你认为在标准Linux服务器上会是多少呢?(例如2个双核Intel Xeon)? - Sam Goldberg
2
我实际上在一台花了大约400美元购买的微型Core 2 Quad Q6600机器上进行了测试,我也可以在C语言中获得不到1微秒的速度(虽然略低于此 - 它仍然远不及更高级的Cisco服务器机器)。所有低于1微秒的数字都是使用普通C应用程序运行基准测试得出的;对于Java,请添加几微秒作为JNI开销的基本值。这也是使用正在轮询的接收线程;您也可以运行非轮询,但是这样会增加一些微秒的信号/线程唤醒延迟。 - strangelydim

2
“关于NIO的O'Reilly书籍(Java NIO,第84页),似乎对内存映射是否保留在内存中有些模糊。也许它只是在说像其他内存一样,如果物理内存不足,则会被交换回磁盘,否则不会?”
Linux中的mmap()调用在操作系统页面缓存区域中分配页面(这些页面定期刷新到磁盘并基于Clock-PRO(LRU算法的近似值)可以被驱逐掉)。因此,对您的问题的答案是 - 是的。除非它被锁定(mlock()),否则内存映射缓冲区可以从内存中驱逐出去(理论上)。这是理论上的。实际上,我认为如果您的系统没有交换,那么这几乎不可能发生。在这种情况下,第一个受害者是页面缓冲区。"

1

我们基本上得出了与您相同的结论。 但是,我对内存映射文件有一个问题,即该内存是否会留在RAM中,还是有时会回写到磁盘(不确定规则是什么)。 O'Reilly关于NIO的书(Java NIO,第84页)似乎模糊地表明内存映射是否保留在内存中。也许它只是说像其他内存一样,如果物理内存不足,则会将其交换回磁盘,否则不会? - Sam Goldberg

1

查看https://github.com/pcdv/jocket

它是本地Java sockets的低延迟替代品,使用共享内存。

现代CPU上2个进程之间的RTT延迟远低于1微秒。


我开始研究Jocket。它似乎使用了几个其他答案中建议的MappedByteBuffer。我已经测试过使用MappedByteBuffer,发现它在IPC方面非常快。但我仍然有一个未解答的问题,即磁盘I/O何时发生(这取决于刷新到磁盘的缓冲区大小而引入非常大的暂停)。 - Sam Goldberg
是的,它使用MappedByteBuffer。我也担心I/O延迟,所以我决定尽可能在“/dev/shm”下创建文件(在Linux下,它被挂载为tmpfs,所以没有I/O)。然而,在我的基准测试中,我无法观察到任何明显的性能差异... - pcdv
谢谢你提供有关/dev/shm的提示。这绝对值得我们尝试。 - Sam Goldberg

1

MemoryMappedFiles并不是低延迟IPC的可行解决方案 - 如果映射的内存段被更新,它最终将被同步到磁盘,从而引入了不可预测的延迟,至少需要几毫秒。对于低延迟,可以尝试使用共享内存+消息队列(通知)或共享内存+信号量的组合。这适用于所有Unix系统,特别是System V版本(而非POSIX),但如果在Linux上运行应用程序,则可以使用POSIX IPC(大多数功能在2.6内核中可用)。是的,您需要JNI来完成此操作。

更新:我忘记了这是JVM - JVM IPC,我们已经有了无法完全控制的GC,因此由于OS文件缓冲区刷新到磁盘而引入额外的几毫秒暂停可能是可以接受的。


如果映射的内存段被更新,最终会同步到磁盘。它是否只被分页到磁盘作为“交换”(即当操作系统没有足够的物理内存将其保留为内存时)? - Sam Goldberg
pdflush 在 Linux 上知道何时执行。 - Vladimir Rodionov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接