3

服务器环境

  • Linux/红帽
  • 6核
  • 爪哇 7/8

关于申请:

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

建筑学:

  • 我们已经使用了 MemoryMaps & Unsafe。在这种情况下,模块 A 写入内存映射文件,模块 B 从文件中读取(两者都保存文件的地址位置)
  • 我们继续并使用了一个无限循环来继续读取,直到从内存映射文件中获得所需的值

问题

  • 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
4

1 回答 1

4
  1. 高负载 CPU 是尽可能低延迟的实际成本。在使用无锁信号的实际架构中,您应该在每个 CPU 插槽上运行不超过一对消费者-生产者线程。一对几乎完全吃掉一个或两个(如果没有固定到启用超线程的单个 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,这是阻塞技术。命名管道比套接字具有更好的延迟特性,但它们实际上是非阻塞的,您必须再次构建类似自旋循环的东西。内存映射文件 + 固有的 Unsafe.getXXX(它是单个 x86 MOV)在延迟和吞吐量方面仍然是最好的 IPC 技术,因为它在读取和写入时不需要系统调用。

  4. 如果您仍将使用无锁和内存映射文件并使用 Unsafe 直接访问,请不要忘记为生产者和消费者设置适当的内存屏障。例如,如果您不确定您的代码是否始终在更高版本的 x86 上运行,请使用“unsafe.getIntVolatile”而不是第一个“unsafe.getInt”。

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

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

于 2015-02-05T22:39:36.190 回答