ChronicleQueue是一个持久的低延迟Java消息传递框架。它适用于高性能的关键任务应用程序。由于ChronicleQueue在本地映射内存上运行,因此它消除了垃圾收集的需要,并为开发人员提供了确定性和高性能。本文将使用开源ChronicleQueue的两个线程相互交换256字节的消息数据。同时,为了尽量减少对磁盘子系统的影响,所有的消息都会存储在共享内存--/dev/shm中。通常,在此类基准测试中,单个生产者线程将消息写入具有纳秒时间戳的队列。另一个消费者线程从队列中读取消息,并在直方图中记录时间增量。生产者保持每秒100,000条消息的恒定输出速率。其中,每条消息中的有效载荷为256字节。由于数据是在100秒的跨度内测量的,因此存在的大部分抖动都可以反映在测量中,并且可以确保具有较高百分位数的数据落在合理的置信区间内。我们的目标主机有一个AMDRyzen95950X,有16个内核,在Linux5.11.0-49-generic#55-UbuntuSMP上以3.4GHz运行。由于这个CPU的2-8个核心是隔离的,操作系统不会自动调度任何用户进程,并且会避免这些核心上的大部分中断。1、下面Java代码展示了生产者内部循环的部分代码:Java//PintheproducerthreadtoCPU2Affinity.setAffinity(2);try(ChronicleQueuecq=SingleChronicleQueueBuilder.binary(tmp).blockSize(blocksize).rollCycle(ROLL_CYCLE).build()){ExcerptAppenderappender=cq.acquireAppender();finallongnano_delay=1_000_000_000L/MSGS_PER_SECOND;for(inti=-WARMUP;i=0)deltas[idx]=System.nanoTime()-startTime;++idx;}}}可以看出,消费者线程会读取每一个nano时间戳,并将对应的延迟记录在一个数组中。这些时间戳稍后会放入直方图中,以便在基准测试完成后进行打印。而且,只有在JVM适当“预热”并且C2编译器有JIT(Just-In-Time)热执行路径后,测量才会开始。2.JVM的各种变体目前,ChronicleQueue可以正式支持所有最新的LTS(LightTaskSchedule)版本,包括Java8、Java11和Java17,因此它们都可以用于基准测试。同时我们也会使用GraalVM的社区版和企业版。以下是我们在测试中使用的特定JVM变体的列表:表1,其中列出了使用的特定JVM变体3.由于基准测试运行100秒,每秒产生100,000个条目,因此产生了100,000个消息*100=1000万条消息在每个基准期间进行采样。直方图将每个样本置于特定的百分位数,例如50%(中值)、90%、99%和99.9%。下表显示了针对这些百分位数的测试收到的消息总数:表2,显示了每个百分位数的消息数对于上表,我们锁定了测量值变化相对较小的区间,对于高达99.99%的百分位数,置信区间可能是合理的。99.999%的百分位数可能需要至少运行半小时左右,而不是仅仅使用100秒,以收集数据以生成具有合理置信区间的任何数据。4.基准测试结果对于每个Java变体,我们运行了以下基准测试:Shellmvnexec:java@QueuePerformance请注意,我们的生产者和消费者线程将被锁定在单独的CPU上运行,分别在2核和4核上运行。以下是它们运行一段时间后的典型进程特征:Shell$topPIDUSERPRNIVIRTRESSHRS%CPU%MEMTIME+COMMAND3216555per.min+20092.3g1.5g1.1gS200.02.30:50.15java可以看出注意生产者和消费者线程在每条消息之间自旋等待,所以每条都消耗一整个CPU核心。如果CPU消耗是一个潜在问题,它的延迟和确定性可以通过在没有消息可用时暂停线程短时间来降低功耗(例如LockSupport.parkNanos(1000))。通常,我们以纳秒(ns)为单位测量测试结果。当然,许多其他类型的延迟测量是以微秒(=1,000ns)甚至毫秒(=1,000,000ns)为单位测量的。这里的1ns大致对应CPU的L1缓存的访问时间。以下所有测试值均为ns中的基准测试结果:表3,显示了使用的各种JDK的延迟结果(*)表示未被ChronicleQueue5官方支持。典型延迟(中值)由上表给出可以看出对于典型(中值)值,各种JDK之间没有显着差异,但OpenJDK11会比其他版本慢30%。其中最快的是GraalVMEE17,它与OpenJDK8和OpenJDK17略有不同。下图包含使用各种JDK变体处理256字节消息时的典型延迟(当然越低越好):图1,显示了各种JDK变体(通常为)延迟(以ns为单位)的中值从图中可以看出,典型(中值)延迟会因运行环境的不同而略有不同,它们的数字相差约5%。6.更高的百分位下面是另一个图表,显示了各种JDK变体的99.99%的百分位延迟(当然越低越好)。查看较高的百分位数,各种受支持的JDK变体之间没有太大差异。GraalVMEE再次稍快一些,但这里的相对差异变小了。而OpenJDK11似乎比其他变体略差(-5%),但其错误增量仍在可接受范围内。图2显示了各种JDK变体的99.99%百分位数延迟(以ns为单位)7.总结根据以上代码的执行逻辑:从主存访问64位数据大约需要100个周期(即相当于大约30ns在当前硬件上)。通过上面的测试对比,我们可以看出,ChronicleQueue通过写入内存映射文件的方式从生产者获取数据并持久化数据,并对线程间通信和happens-before保证进行了适当的内存保护。然后将数据提供给消费者。对于256字节的消息,所有这些通常在600ns左右发生,而单个64位内存访问则需要30ns。ChronicleQueue产生的这些延迟比较结果令人印象深刻。可以看出,OpenJDK17和GraalVMEE17都提供了最好的延迟结果,是应用程序的首选。当然,如果你需要抑制异常值,或者尽可能降低整体延迟,那么GraalVMEE17会更合适。原文链接:https://dzone.com/articles/which-jvm-version-is-the-fastest控制外部资源和风险,重点传播网络和信息安全知识和经验;持续以博文、专题、翻译等形式分享前沿技术和新知识;经常在线上和线下开展信息安全培训和讲座。