Java NIO选择器最小可能延迟

Java NIO选择器最小可能延迟,java,real-time,messaging,nio,zeromq,Java,Real Time,Messaging,Nio,Zeromq,我正在Linux上通过环回(127.0.0.1)使用优化的JavaNIO选择器进行一些基准测试 我的测试非常简单: 一个程序将UDP数据包发送给另一个程序,该程序将数据包回传给发送方,并计算往返时间。下一个数据包仅在前一个数据包被确认(返回时)时发送。在执行基准测试之前,使用数百万条消息进行适当的预热。消息有13个字节(不包括UDP头) 对于往返时间,我得到以下结果: 最小时间:13微秒 平均时间:19微秒 75%百分位数:18567纳米 90%百分位数:18789纳米 99%百分位数:1

我正在Linux上通过环回(127.0.0.1)使用优化的JavaNIO选择器进行一些基准测试

我的测试非常简单:

  • 一个程序将UDP数据包发送给另一个程序,该程序将数据包回传给发送方,并计算往返时间。下一个数据包仅在前一个数据包被确认(返回时)时发送。在执行基准测试之前,使用数百万条消息进行适当的预热。消息有13个字节(不包括UDP头)
对于往返时间,我得到以下结果:

  • 最小时间:13微秒
  • 平均时间:19微秒
  • 75%百分位数:18567纳米
  • 90%百分位数:18789纳米
  • 99%百分位数:19184纳米
  • 99.9%百分位数:19264纳米
  • 99.99%百分位数:19310纳米
  • 99.999%百分位数:19322纳米
但这里的陷阱是我正在发送一百万条消息

如果我只旋转10条消息,我会得到非常不同的结果:

  • 最小时间:41微秒
  • 平均时间:160微秒
  • 75%百分位数:150701纳米
  • 90%百分位数:155274纳米
  • 99%百分位数:159995纳米
  • 99.9%百分位数:159995纳米
  • 99.99%百分位数:159995纳米
  • 99.999%百分位数:159995纳米
如果我错了,请纠正我,但我怀疑一旦我们获得NIO选择器,响应时间就会变得最佳。然而,如果我们发送的消息间隔足够长,我们就要付出唤醒选择器的代价

如果我只发送一条信息,我会收到150到250微秒的不同时间

因此,我向社区提出的问题是:

1-是我的最短时间13微秒,平均19微秒,最适合此往返数据包测试。看起来我已经被打败了,所以我可能错过了一些东西。从这个基准测试来看,ZeroMQ在标准内核上的平均时间为49微秒(99%的百分比)=>

2-当我旋转一条或几条消息时,我能做些什么来提高选择器的反应时间吗?150微米看起来不太好。或者我应该假设在prod环境中,选择器不完全正确吗


通过在selectNow()周围忙碌地旋转,我可以获得更好的结果。发送少量数据包仍然比发送大量数据包更糟糕,但我认为我现在达到了选择器性能极限。我的结果:

  • 发送一个数据包,我得到了一致的65微秒往返时间
  • 发送两个数据包平均往返时间约为39微秒
  • 发送10个数据包平均往返时间约为17微秒
  • 发送10000个数据包,平均往返时间约为10098纳秒
  • 发送100万个数据包,平均往返时间为9977纳秒
结论

  • 因此,看起来UDP数据包往返的物理屏障平均为10微秒,尽管我有一些数据包在8微秒(分钟)内完成了往返

  • 由于忙着旋转(感谢彼得),我能够从平均200微秒到平均65微秒,一个包

  • 不知道为什么ZeroMQ会比这更重要。(编辑:可能是因为我在同一台机器上通过环回进行测试,而ZeroMQ使用的是两台不同的机器?)


您经常会看到,在这种情况下,唤醒线程的成本可能非常高,这不仅是因为唤醒线程需要时间,而且在缓存和恢复后,线程运行速度会慢2-5倍,持续数十微秒

我过去避免这种情况的方法是忙着等待。不幸的是,selectNow每次调用时都会创建一个新集合,即使它是一个空集合。这会产生太多垃圾,不值得使用

解决这个问题的一种方法是在非阻塞套接字上进行忙等待。这并不能很好地扩展,但可以为您提供最低的延迟,因为线程不需要唤醒,并且在此之后运行的代码更可能位于缓存中。如果您也使用线程亲和力,它可以减少对线程的干扰

我还建议您尽量减少代码锁和垃圾。如果您这样做,您可以在Java中有一个进程,它在100微秒90%的时间内发送对传入数据包的响应。这将允许您在每个数据包到达时以100 Mb的速度处理它们(由于带宽限制,最多相隔145微秒),对于1 Gb的连接,您可以非常接近



如果您想在爪哇的同一个框中进行快速进程间通信,您可以考虑这样的事情:使用共享内存传递具有小于200强<纳米秒< /强>的往返延迟(很难有效地使用套接字)的消息。它还可以保存数据,如果您只想快速生成日志文件,它非常有用。

如果您正确调整选择器,您可以在不到2微秒的时间内获得Java中的套接字间通信。以下是我对256字节UDP数据包的单向结果:

Iterations: 1,000,000
Message Size: 256 bytes
Avg Time: 1,680 nanos
Min Time: 1379 nanos
Max Time: 7020 nanos
75%: avg=1618 max=1782 nanos
90%: avg=1653 max=1869 nanos
99%: avg=1675 max=1964 nanos
99.9%: avg=1678 max=2166 nanos
99.99%: avg=1679 max=5094 nanos
99.999%: avg=1680 max=5638 nanos

我在文章中更多地讨论了Java NIO和reactor模式。

我认为这在很大程度上是由于热点JVM预热时间,而不是选择器的行为。谢谢@EJP,但我确实在服务器模式下对JVM进行了一些预热。在发送触发基准测试的消息之前,我发送了数百万条消息。为什么你认为会发生这种情况=>“如果我只发送一条消息,我会收到150到250微秒的不同时间。”你可以说我疯了,但你为什么不重新实现你的(描述)用C编写一个简短的程序,看看它的性能。@nosenseal称我为nuts,但我希望有一个非阻塞选择器的C实现,我的Java程序可以通过JNI调用它。“在什么地方有这么强大的东西吗?”朱莉:我的建议