2个JVM之间的低CPU使用率轮询架构



服务器环境

  • Linux/ReHat
  • 6芯
  • Java 7/8

关于应用程序:

  • 我们正在使用Java开发一个低延迟(7-8毫秒)的高速交易平台
  • 有两个模块A&B每个都在自己的JVM上运行
  • B从A获取数据

体系结构:

  • 我们使用了MemoryMaps&不安全的在这种情况下,模块A写入存储器映射文件&模块B从文件中读取(两者都持有文件的地址位置)
  • 我们继续前进&使用无休止的while循环继续读取,直到从内存映射文件中获得所需值

问题

  • CPU利用率飙升至100%&在其生命周期之前保持不变

问题:

是否有一种更复杂的方法来保持对内存映射文件中的值的轮询,该方法涉及最小开销、最小延迟&最低CPU利用率?请注意,每微秒的延迟都会降低的性能

代码段

模块B的代码片段(从内存映射文件中轮询和读取的无休止while循环)位于下方

FileChannel fc_pointer = new RandomAccessFile(file, "rw").getChannel();
      MappedByteBuffer mem_file_pointer =fc_pointer.map(FileChannel.MapMode.READ_ONLY, 0, bufferSize);
      long address_file_pointer = ((DirectBuffer) mem_file_pointer).address();

    while(true)
    {
        int value_from_memory_mapped_file = unsafe.getInt(address_file_pointer);
        if (value_from_memory_mapped_file .. is different from the last read value)
        {
            //do some operation.... 
        //exit the routine;
        }
        else
        {
            continue;
        }
}//end of while
  1. 高负载CPU是最低延迟的实际成本。在使用无锁信令的实用体系结构中,每个CPU套接字运行的线程不应超过Consumer-Productor对。一对几乎完全吃掉一个或两个核心(如果不是在启用超线程的情况下固定到单个Intel CPU核心,则每个线程一个核心)(这就是为什么在大多数情况下,当您为许多客户端构建超低延迟服务器系统时,您必须考虑水平可扩展性)。顺便说一句,在性能测试和禁用电源管理之前,不要忘记使用"任务集"将每个进程固定到特定的核心。

  2. 有一个众所周知的技巧,当你在长时间旋转后锁定消费者,但没有结果。但你必须花一些时间停车,然后打开线。当然,这是一个偶尔延迟增加的时刻,但当线程停止时,CPU核心是空闲的。例如,请参见:http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf(8.4.4较长周期的同步)此外,可以在这里找到java不同等待策略的一个很好的例子:https://github.com/LMAX-Exchange/disruptor/wiki/Getting-Started(替代等待策略)

  3. 如果您谈论的是毫秒(ms),而不是微秒(µs),您可以尝试通过环回进行TCP套接字通信。它增加了大约10µs,将少量数据从生产者传递给消费者,这是一种阻塞技术。命名管道比套接字具有更好的延迟特性,但它们实际上是非阻塞的,您必须再次构建类似spinloop的东西。内存映射文件+内部Unsafe.getXXX(它是一个单独的x86 MOV)在延迟和吞吐量方面仍然是最好的IPC技术,因为它在读写时不需要系统调用。

  4. 如果您仍然要使用无锁和内存映射文件,并使用不安全直接访问,请不要忘记为生产者和消费者设置适当的内存障碍。例如,如果您不确定代码总是在以后的x86上运行,请使用"unsafe.getIntVolatile"而不是第一个"unsafet.getInt"。

  5. 如果您发现每对Producer Consumer的意外CPU利用率不应超过30-40%(2个已利用的内核对应6个内核的CPU),则必须使用标准工具来检查其他内核上运行的内容和整体系统性能。如果您看到与映射文件相关联的密集型IO,请确保将其映射到tmpfs文件系统,以防止实际的磁盘IO。检查内存总线加载和三级缓存未命中的"最胖"进程,因为正如我们所知,CPU时间=(CPU exec时钟周期+_memory_sstall_cycles_)*时钟周期时间

最后,还有一个非常相似和有趣的开源项目,其中有一个如何使用内存映射文件的好例子:http://openhft.net/products/chronicle-queue/

最新更新