- 后端
- 异步编程
【免费下载链接】reactor-core
Non-Blocking Reactive Foundation for the JVM
导读
本文以 benchmarks/README.md 为核心,系统讲解 reactor-core 仓库中benchmarks模块的 JMH(Java Microbenchmark Harness)基准测试任务用法,包括如何精确指定被测类、如何附加 profiler 分析 GC 与线程行为、如何切换报告格式、如何注入 JVM 参数,以及如何借助jmhBaseline与最新发布版本进行 A/B 对比。读完本文,你将能够在本地复现 reactor-core 的性能回归验证流程,并理解这些 Gradle 任务底层是如何拼装 JMH 命令行参数的。
一、benchmarks 模块与 JMH 基础设施概览
benchmarks是 reactor-core 仓库中的一个独立 Gradle 子项目(在 settings.gradle 中通过include 'benchmarks', ...注册),专门用于存放针对reactor-core及reactor-core-micrometer的 JMH 基准测试。它的 Gradle 配置位于 benchmarks/build.gradle,其核心依赖与任务关系如下:
- 依赖
org.openjdk.jmh:jmh-core(版本 1.37,见 gradle/libs.versions.toml),并通过annotationProcessor libs.jmh.generatorAnnprocess使用 JMH 注解处理器自动生成*_jmh运行类; - 使用
compileOnly引入reactor-perfBaseline-core等基线依赖,目的是防止在编写 benchmark 时意外使用新版本 API(见 benchmarks/build.gradle 的注释 "Use the baseline to avoid using new APIs in the benchmarks"); - 定义了三个关键任务:
jmh、jmhBaseline与jmhProfilers,其中jmh和jmhBaseline都继承自自定义任务类 JmhExecTask(位于 buildSrc 中),jmhProfilers则直接以org.openjdk.jmh.Main -lprof方式列出所有可用 profiler(见 benchmarks/build.gradle)。
由于任务定义在benchmarks子项目内,从仓库根目录运行需写为./gradlew :benchmarks:jmh ...,而原文档中的../gradlew jmh ...则是约定在benchmarks/目录下执行的形式,二者等价。
二、精确控制被测 Benchmark:include、fullInclude 与 exclude
JMH 默认会运行所有@Benchmark方法,而 reactor-core 的基准测试往往数量较多、耗时较长。因此第一步通常是缩小范围。
2.1 按子串匹配:--include
原文档给出的第一种方式是按子串过滤,Gradle 会自动在传入值前后各加一个.*通配符:
../gradlew jmh --include="(BenchmarkPrimary|OtherBench)"上述命令实际匹配的是类名(JMH 内部按 "类名.方法名" 形式的 benchmark 全名做正则匹配),等效于正则.*(BenchmarkPrimary|OtherBench).*,可以一次匹配多个含不同关键字的 benchmark。
2.2 自己写完整正则:--fullInclude
如果你需要更精细的控制,可以直接传入完整的 JMH 风格正则:
../gradlew jmh --fullInclude=.*MyBenchmark.*2.3 反向排除:--exclude
虽然原文档未列出,但从 JmhExecTask.java 的源码可以看到任务还提供了exclude选项,它会被映射为 JMH 的-e参数:
../gradlew jmh --exclude=.*SomeSlowBench.*三个选项在任务实现中的优先级关系(见 JmhExecTask.java)是:若设置了include则使用.*<include>.*;否则若设置了fullInclude则直接使用该正则;exclude独立生效,两者可组合使用。
三、附加 Profiler:定位 GC、线程与 JIT 瓶颈
JMH 本身只负责测量吞吐量或耗时,要回答“时间花在哪了”,需要挂载 profiler。
../gradlew jmh --profilers="gc,stack"--profilers接受逗号分隔的多个 profiler 名称,底层实现会对每个名称依次追加一个-prof <name>参数(见 JmhExecTask.java)。
原文档重点推荐了四个常用 profiler:
| Profiler | 用途 |
|---|---|
comp | JitCompilations,查看 JIT 编译情况,用于微调迭代次数 |
stack | 统计哪些方法耗时最多(热点方法分布) |
gc | 打印垃圾回收统计信息(分配率、GC 次数与耗时) |
hs_thr | 线程使用情况(HotSpot 线程状态统计) |
要查看完整 profiler 清单,运行专门的列出任务:
../gradlew jmhProfilers该任务即执行org.openjdk.jmh.Main -lprof(见 benchmarks/build.gradle),会打印诸如gc、stack、comp、hs_thr、async、perfasm、javaProfiler等全部可用选项,供你按需选用。
四、报告格式:从 JSON 切换到 CSV/TEXT
JMH 默认输出 JSON 格式报告(DEFAULT_FORMAT = "json",见 JmhExecTask.java),原文档允许切换为以下五种之一:
./gradlew jmh --format=csv--format的合法取值为CSV、JSON、NONE、SCSV、TEXT:
CSV/SCSV:便于导入 Excel 或 pandas 做后处理;JSON:默认,结构完整,适合程序化解析;TEXT:人类可读的控制台式文本;NONE:不生成机器可读报告。
在任务实现中,该参数同时决定结果文件路径:build/reports/<任务名>/result.<format>(见 JmhExecTask.java)。也就是说,用--format=csv跑jmh任务后,报告位于benchmarks/build/reports/jmh/result.csv,供持续集成或人工对比读取。
五、注入 JVM 参数
某些 benchmark 需要特定的系统属性或 JVM 开关,可通过--jvmArgs传入(注意参数按空格拆分,多个参数用空格分隔,见 JmhExecTask.java):
../gradlew jmh --jvmArgs="-Dtest.cluster=local"仓库中的 benchmark 也大量依赖此类 JVM 开关。例如 AssemblyTraceBenchmark 通过@Fork(jvmArgsAppend = ...)在 fork 的 JVM 上分别设置-Dreactor.trace.operatorStacktrace=true/false来对比启用/关闭操作符堆栈跟踪时的开销,并追加-XX:-OmitStackTraceInFastThrow防止 JIT 优化掉异常堆栈。这类测试场景,配合--jvmArgs可以进一步叠加自定义属性。
需要说明的是,任务实现还会无条件追加两个堆内存参数(见 JmhExecTask.java):
-Xmx3072m -Xms3072m即每个 fork 出的 JVM 固定使用 3GB 堆,避免因默认堆过小导致 GC 干扰测量结果。
六、验证模式:只检查 benchmark 能否运行
完整跑一轮基准测试往往需要数十分钟甚至更久。改动 benchmark 代码后想快速确认“能编译、能跑通、不出错”,用验证模式:
../gradlew jmh --verify=true从源码看,verify模式会把 fork、预热迭代、测量迭代全部压到最小值(见 JmhExecTask.java):
-f 1:只 fork 一次;-wi 1:仅 1 轮预热迭代;-i 1:仅 1 轮测量迭代。
同时任务始终追加-foe true(fail-on-error,任何 benchmark 抛错立即失败)与-v NORMAL(输出级别),因此验证模式下任何异常都会直接导致构建失败,便于在 CI 或本地快速回归。
七、与最新发布版基线对比:jmhBaseline
性能优化的关键问题是:“我的改动相比上一个版本是变快还是变慢?”为此仓库提供了配套任务jmhBaseline,它用最新发布版本(而非当前工作区代码)运行同一批 benchmark:
../gradlew jmh jmhBaseline --include=MyBenchmark在 benchmarks/build.gradle 中,jmh与jmhBaseline分别使用两组独立配置:
jmh的 classpath 为sourceSets.main.runtimeClasspath + configurations.current,current指向当前模块project(':reactor-core')与project(':reactor-core-micrometer')(见 benchmarks/build.gradle);jmhBaseline使用configurations.baseline,即通过libs.reactor.perfBaseline.core解析到的发布版坐标,当前版本配置为baselinePerfCore = "3.8.7"(见 gradle/libs.versions.toml),并设置changing = true、缓存时间为 0 秒,确保总是拉取最新基线。
两个任务产生的报告分别落在build/reports/jmh/result.json与build/reports/jmhBaseline/result.json,对比两份 JSON 即可量化本次改动的性能影响。这也是 reactor-core 维护者在发布前验证性能回归的标准流程。
八、仓库内典型 Benchmark 速览
理解了任务用法之后,快速浏览仓库中已有的 benchmark 可以帮助你写出风格一致、结果可信的新用例:
- FluxPublishBenchmark:使用
@Param({"0", "10", "1000", "100000"})在四种数据规模下测量Flux.range().publish().autoConnect(availableProcessors())的多线程发布吞吐;@Threads(Threads.MAX)让线程数与 CPU 核数对齐,配合@Fork(value = 1)固定 fork 次数,可对比不同rangeSize的扩展性。 - MonoAllBenchmark:用
Flux.range(0, rangeSize).all(i -> i < Integer.MAX_VALUE).block()测量all()谓词短路运算在不同数据规模下的平均耗时(纳秒级输出)。 - BoundedElasticBusyStructureBenchmark:同一被测场景下对比当前
Schedulers.newBoundedElastic(...)与仓库保留的旧实现OldBoundedElasticScheduler(其任务队列基于PriorityBlockingQueue,见 OldBoundedElasticScheduler.java),用于验证调度器数据结构改造带来的收益。 - FluxBufferTimeoutBenchmark:通过自定义
JmhSubscriber模拟两种背压请求模式(一次请求Long.MAX_VALUE的unlimited与逐条request(1)的oneByOne),测量bufferTimeout在不同缓冲大小下的行为差异。 - CheckpointBenchmark:对比
checkpoint("desc", true)(完整堆栈跟踪)与轻量checkpoint("identifier")两种调试模式对吞吐量(Throughput)与采样耗时(SampleTime)的影响,直接指导生产环境调试开关的选型。 - AssemblyTraceBenchmark:以递归方式构造不同深度(
@Param({"10", "40", "80"}))的 operator 链,在开启/关闭reactor.trace.operatorStacktrace两种模式下测量装配期堆栈跟踪的开销。
这些用例普遍遵循同一套 JMH 规范:类级标注@BenchmarkMode、@Warmup、@Measurement、@Fork、@OutputTimeUnit与@State(Scope.Benchmark),方法级使用Blackhole消费结果防止死代码消除。新写 benchmark 时照搬这套模板即可。
九、执行与报告速查
| 需求 | 命令(在benchmarks/下执行) | 说明 |
|---|---|---|
| 按子串运行 | ../gradlew jmh --include="Benchmark" | 自动加.*通配 |
| 按正则运行 | ../gradlew jmh --fullInclude=.*MyBenchmark.* | 完整 JMH 正则 |
| 附加 profiler | ../gradlew jmh --profilers="gc,stack" | 逗号分隔多个 |
| 列出全部 profiler | ../gradlew jmhProfilers | 等价jmh -lprof |
| 切换报告格式 | ./gradlew jmh --format=csv | CSV/JSON/NONE/SCSV/TEXT |
| 注入 JVM 参数 | ../gradlew jmh --jvmArgs="-Dkey=value" | 空格分隔多个参数 |
| 快速验证 | ../gradlew jmh --verify=true | 1 fork、1 预热、1 测量 |
| 对比基线 | ../gradlew jmh jmhBaseline --include=MyBenchmark | 基线为最新发布版 |
几点补充提醒:
- 结果文件位置:
jmh与jmhBaseline的报告分别写入build/reports/jmh/result.<format>与build/reports/jmhBaseline/result.<format>(见 JmhExecTask.java),对比时注意区分目录; - 堆内存固定 3GB:任务会自动追加
-Xmx3072m -Xms3072m,若需调整只能覆盖 fork 参数而非直接覆盖该内存设置; - fail-on-error 恒定开启:
-foe true保证任何 benchmark 异常都会使构建失败,适合接入 CI; - 验证模式结果无参考意义:
--verify=true只用于“能否运行”的检查,其数值因迭代过少不具备统计价值,正式数据应以默认完整模式产出为准。
通过组合上述选项,你可以在不改动任何仓库代码的前提下,完成从“精确跑某个 benchmark”到“当前分支 vs 最新发布版”的完整性能验证闭环,这正是 reactor-core 对每个 PR 进行性能守护的实践基础。
- 后端
- 异步编程
【免费下载链接】reactor-core
Non-Blocking Reactive Foundation for the JVM
相关推荐
Reactor Core 性能基准测试:JMH 实战指南
Reactor Core 性能基准测试:JMH 实战指南 Reactor Core 作为 JVM 平台上领先的非阻塞响应式编程框架,其性能表现直接影响着高并发应
后端异步编程Apache Kafka JMH 基准测试模块(jmh-benchmarks)实战指南:运行、调参与编写微基准
Apache Kafka JMH 基准测试模块(jmh benchmarks)实战指南:运行、调参与编写微基准 本文以 Apache Kafka 仓库中的 jm
消息队列流处理数据集成存储Reactor Core性能终极指南:JMH基准测试完整实战
Reactor Core性能终极指南:JMH基准测试完整实战 在当今高并发应用开发中,性能优化已成为不可或缺的关键环节。Reactor Core作为JVM平台上
后端异步编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考