RocksJava JMH 基准测试完全指南:编译、运行与内置基准集源码解析 RocksJava JMH 基准测试完全指南编译、运行与内置基准集源码解析【免费下载链接】rocksdbA library that provides an embeddable, persistent key-value store for fast storage.项目地址: https://gitcode.com/gh_mirrors/ro/rocksdb本文以 java/jmh/README.md 为骨架系统讲解 RocksDB Java 绑定RocksJava内置 JMHJava Microbenchmark Harness微基准测试工程rocksdbjni-jmh的完整使用流程如何编译出可运行的 benchmarks uber-jar、如何针对本地修改的 rocksdbjni SNAPSHOT 版本进行替换安装、如何运行并利用 JMH 运行时参数控制测试同时结合仓库内四个基准类的源码逐一解析 Get、Put、MultiGet、Comparator 基准的实现原理与可调参数。读完本文你可以独立在本仓库中复现这些微基准测试并学会编写自己的 RocksJava 基准用例。一、rocksdbjni-jmh 是什么rocksdbjni-jmh是 RocksDB 仓库中专门面向 RocksJava 功能的微基准测试工程位于 java/jmh 目录。它基于 JMHJava Microbenchmark Harness编写用于量化 RocksDB Java API 在典型读写场景下的性能表现。与仓库中的 C 基准工具db_bench见 tools/db_bench.cc不同jmh 工程关注的是JNI 边界上的 Java 层开销同样的逻辑操作通过 Java 的byte[]、ByteBuffer堆外 Direct Buffer等不同数据载体传递给 C 层性能存在可观测差异这正是本工程要度量的核心问题。该工程是一个独立的 Maven 项目其 Maven 坐标定义在 java/jmh/pom.xmlgroupIdorg.rocksdbartifactIdrocksdbjni-jmhversion1.0-SNAPSHOT项目描述JMH Benchmarks for RocksDB Java API工程依赖三个组件rocksdbjniRocksDB 的 Java 原生绑定库、jmh-core与jmh-generator-annprocessJMH 核心与注解处理器后者负责在编译期扫描Benchmark方法生成基准运行骨架依赖版本见 java/jmh/pom.xml。当前仓库的 pom.xml 中声明的rocksdbjni依赖版本为9.0.0JMH 版本为1.22Java 编译级别为 17project.build.source与project.build.target均为 17需要 JDK 17 及以上环境。二、编译构建可运行的 benchmarks JAR2.1 直接编译使用 pom.xml 声明的 rocksdbjni 版本在java/jmh目录下执行标准 Maven 打包命令即可$ mvn package打包流程由 java/jmh/pom.xml 中的插件链驱动maven-compiler-plugin 3.8.1以 Java 17 级别编译源码同时触发 JMH 注解处理器jmh-generator-annprocessprovided作用域为每个标注了Benchmark的类生成*_jmhType等运行时代理类maven-shade-plugin 3.2.1在package阶段将所有依赖rocksdbjni、JMH 运行时等打入同一个 uber-jar并把 Main-Class 设置为org.openjdk.jmh.Main同时剔除签名文件META-INF/*.SF、META-INF/*.DSA、META-INF/*.RSA避免出现 “Invalid signature file” 启动错误license-maven-plugin 3.0校验所有源码文件携带 java/jmh/LICENSE-HEADER.txt 声明的许可证头Apache 2.0 / GPLv2 双许可不满足会直接构建失败。shade 插件配置的finalName为${project.artifactId}-${project.version}-${uberjar.name}其中uberjar.name属性值为benchmarks因此最终生成的可执行 JAR 路径为target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar2.2 测试本地修改安装 SNAPSHOT 版本的 rocksdbjniREADME 特别强调了一个关键约束jmh 工程使用pom.xml的version元素声明的 rocksdbjni 构建产物。如果你修改了 RocksDB 的 C/JNI 代码例如改动了 java/rocksjni 下的 JNI 实现那么先用仓库的 Java 构建系统java/Makefile 中的java目标等构建出带 SNAPSHOT 版本号的本地 JNI jar将本地 jar 安装到本地 Maven 仓库更新java/jmh/pom.xml中rocksdbjni依赖的version使其指向刚才安装的 SNAPSHOT 版本再执行mvn package基准测试就会链接到你本地改动过的原生库。以 README 中 OSX 平台、版本号8.11.0-SNAPSHOT为例安装本地 jar 的命令如下$ mvn install:install-file -Dfile./java/target/rocksdbjni-8.11.0-SNAPSHOT-osx.jar \ -DgroupIdorg.rocksdb \ -DartifactIdrocksdbjni \ -Dversion8.11.0-SNAPSHOT \ -Dpackagingjar说明-Dfile指向你构建出的 JNI jar 的完整路径。RocksJava 构建产物位于java/target/目录jar 名称带平台后缀如-osx、-linux具体以你本机构建产物为准-DgroupId与-DartifactId必须与 pom.xml 中依赖声明一致org.rocksdb:rocksdbjni否则 Maven 无法解析-Dversion需与你准备写入 pom.xml 的版本号严格对应安装完成后修改 java/jmh/pom.xml 中artifactIdrocksdbjni/artifactId对应的version元素再执行mvn package。需要说明的是README 示例中的8.11.0只是当时写作时期的版本号。当前仓库 include/rocksdb/version.h 定义的 C 版本为 11.11.0而 java/jmh/pom.xml 当前声明的rocksdbjni依赖为9.0.0。实际操作时版本号请以你本地构建产物和 pom.xml 的当前内容为准只需保证三步之间的版本号一致即可。2.3 常见编译问题Java 版本过低pom 要求 Java 17低于该版本会在 maven-compiler-plugin 编译阶段报错许可证头缺失新增的基准源码文件必须携带与仓库其他 Java 文件一致的许可证头否则 license-maven-plugin 的strictCheck会中断构建rocksdbjni 依赖无法解析如果 pom.xml 中版本指向的是尚未发布到 Maven 中央仓库的 SNAPSHOT必须先用mvn install:install-file完成本地安装。三、运行启动 JMH 基准编译完成后通过java -jar直接启动$ java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar不带任何参数时JMH 会依次运行工程内所有Benchmark方法当前共有 4 个基准类详见下一节每项默认执行多轮 warmup 与 measurement 迭代最终输出吞吐量/平均耗时等指标与置信区间。README 特别提示在命令后追加-help可以查看 JMH 的全部运行时选项$ java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar -help借助 JMH 运行时参数你可以只跑指定基准、调整迭代策略或线程数。例如按类名或方法名过滤要执行的基准、控制 fork 数、预热迭代数与测量迭代数。工程内也提供了编程式构造运行参数的参考实现在 MultiGetBenchmarks.java 的main方法中通过OptionsBuilder设置了.include(...)过滤基准类、.forks(1)fork 数、.jvmArgs(-ea)JVM 参数、.warmupIterations(1)、.measurementIterations(2)、.param(...)覆盖参数与.output(jmh_output)结果输出文件展示了以Runner编程式驱动基准的完整写法。四、内置基准集源码解析工程共有 4 个基准类均位于 java/jmh/src/main/java/org/rocksdb/jmh全部使用State(Scope.Benchmark)级别的共享状态MultiGetBenchmarks的字段状态为Scope.Thread并在Setup(Level.Trial)中完成数据库打开与数据灌入、在TearDown(Level.Trial)中完成资源释放与临时目录清理。所有基准在 setup 阶段都遵循同一套模式RocksDB.loadLibrary()加载原生库 →Files.createTempDirectory(...)创建临时数据库目录 → 构造DBOptionssetCreateIfMissing(true)与setCreateMissingColumnFamilies(true)→ 按需创建列族描述符 →RocksDB.open(...)打开数据库 → 灌入测试数据 → 必要时flush将内存数据刷入 SST。teardown 阶段则逆序关闭ColumnFamilyHandle、db、options等资源并调用 FileUtils.java 的delete递归清理临时目录保证多次运行互不干扰。4.1 GetBenchmarks单点读取源码见 GetBenchmarks.java。它通过Param声明了四组可变参数组合展开后形成多维测试矩阵参数取值含义columnFamilyTestTypeno_column_family/1_column_family/20_column_families/100_column_families列族规模0、1、20、100 个列族keyCount1000/100000每个列族写入的键数量keySize12/64/128键的字节长度valueSize64/1024/65536值的字节长度Setup 阶段按列族数量创建名为cf1、cf2……的列族见 GetBenchmarks.java向每个列族写入keyCount条形如key0..keyN的数据并 flush 到磁盘。读取时通过AtomicInteger的compareAndSet实现无锁的键轮转避免并发线程读到相同键多列族场景下用cfHandlesIdx轮转选择目标列族不追求完美均匀分布注释中明确说明 doesnt ensure a perfect distribution, but its ok。三个Benchmark方法分别度量三种不同的取值 API 开销见 GetBenchmarks.javaget()最简调用db.get(cfHandle, keyArr)每次从 JNI 层返回新分配的byte[]preallocatedGet()db.get(cfHandle, keyArr, valueArr)使用预分配的目标缓冲区接收结果避免每次返回新数组的分配开销preallocatedByteBufferGet()db.get(cfHandle, readOptions, keyBuf, valueBuf)使用堆外ByteBufferByteBuffer.allocateDirect读写度量 Direct Buffer 路径下的 JNI 数据传递成本。方法内保留了一段被注释掉的正确性校验代码可作为测试基准正确性的参考。4.2 PutBenchmarks写入路径源码见 PutBenchmarks.java。参数与 Get 基准基本一致另有一个bufferListSize当前固定为16控制缓冲区池大小。写入键由内部Counter状态类的AtomicInteger自增生成保证每次写入的键互不重复。为避免Benchmark方法内的对象分配污染测量结果Put 基准实现了一个简单的缓冲区借用池见 PutBenchmarks.javaborrow从池中取出空闲缓冲区池空时休眠等待repay用完后归还全程在synchronized保护下进行。三个Benchmark方法对比三种写入 API见 PutBenchmarks.javaput()db.put(cfHandle, keyBuf, valueBuf)不带显式WriteOptions使用默认写选项putByteArrays()db.put(cfHandle, new WriteOptions(), keyBuf, valueBuf)显式传入新建的WriteOptions度量每次构造写选项对象的开销putByteBuffers()db.put(cfHandle, new WriteOptions(), keyBuf, valueBuf)键值均以堆外ByteBuffer传入度量 Direct Buffer 写入路径的开销。通过对比这三个方法的结果可以定位 RocksJava 写入链路中 JNI 数据拷贝方式 与 选项对象构造 各自的性能占比。4.3 MultiGetBenchmarks批量读取源码见 MultiGetBenchmarks.java。除列族规模外它的参数侧重批量语义参数取值含义keyCount10000/25000/100000写入的键总数multiGetSize10/100/1000/10000每次批量读取的键数量valueSize16/64/250/1000/4000/16000值的字节长度keySize16键的字节长度Setup 阶段除了向默认列族与各命名列族灌数外还预构建了两个列族句柄列表见 MultiGetBenchmarks.javadefaultCFHandles全部指向默认列族与randomCFHandles随机指向 20 个命名列族中的一个分别用于显式列族与随机列族批量读取的对照。四个Benchmark方法见 MultiGetBenchmarks.javamultiGetList10()db.multiGetAsList(keys)批量键不指定列族走默认列族返回byte[]列表multiGetListExplicitCF20()db.multiGetAsList(columnFamilyHandles, keys)批量键显式绑定默认列族句柄multiGetListRandomCF30()批量键随机绑定到不同命名列族度量跨列族批量读取的开销multiGetBB200()db.multiGetByteBuffers(keys, values)使用堆外ByteBuffer批量读写通过返回的ByteBufferGetStatus校验每个键的Status.Code是否为Ok且值长度符合预期。每个方法都会对结果长度/状态做断言式校验任何不满足假设的返回都会抛出RuntimeException从机制上防止基准在错误数据上得出无意义数字。4.4 ComparatorBenchmarks比较器开销源码见 ComparatorBenchmarks.java。它专门度量不同比较器实现对写入路径的影响通过Param声明了 17 种比较器配置组合见 ComparatorBenchmarks.java可归为三类native 内置比较器native_bytewise字节序与native_reverse_bytewise逆字节序通过BuiltinComparator.BYTEWISE_COMPARATOR/REVERSE_BYTEWISE_COMPARATOR设置比较逻辑完全在 C 层执行Java 实现的 bytewise 比较器java_bytewise_*系列由org.rocksdb.util.BytewiseComparator实现Java 实现的逆序比较器java_reverse_bytewise_*系列由org.rocksdb.util.ReverseBytewiseComparator实现。Java 比较器名称中还编码了三组ComparatorOptions调优维度解析逻辑见 ComparatorBenchmarks.java缓冲区类型directsetUseDirectBuffer(true)使用堆外 Direct Buffer 传递键数据与non-directsetUseDirectBuffer(false)缓冲区复用策略reused-64setMaxReusedBufferSize(64)允许复用最大 64 字节的缓冲区与no-reusesetMaxReusedBufferSize(-1)禁用复用并发同步方式adaptive-mutexReusedSynchronisationType.ADAPTIVE_MUTEX、non-adaptive-mutexReusedSynchronisationType.MUTEX、thread-localReusedSynchronisationType.THREAD_LOCAL。基准方法只有put()见 ComparatorBenchmarks.java不断写入key0..keyN。由于写入过程中 memtable 需要按比较器维护键序该基准可以清晰揭示 Java 比较器经 JNI 往返调用与原生比较器之间的性能差距以及 Direct Buffer、缓冲区复用、同步策略对 Java 比较器开销的缓解效果。五、编写自己的基准用例基于上述工程结构新增一个基准类的步骤是在org.rocksdb.jmh包下新建 Java 类放在 java/jmh/src/main/java/org/rocksdb/jmh 目录声明State用Param声明可变参数用Setup/TearDown完成数据库生命周期管理并为每个待测操作添加Benchmark方法参照工程内既有类的写法在Setup中先RocksDB.loadLibrary()在TearDown中逆序关闭所有原生资源句柄、数据库、选项对象并清理临时目录避免资源泄漏影响测量若新类需要测试本地 JNI 改动按本文 2.2 节的流程安装 SNAPSHOT 并更新 pom.xml 版本重新执行mvn packagelicense 插件会要求新文件携带许可证头JMH 注解处理器会自动为你的Benchmark方法生成运行骨架随后即可用java -jar启动运行。在编辑基准时建议遵循工程内已有的两个原则一是复用缓冲区如 Put 基准的借用池、Get 基准的预分配数组把分配开销排除在测量之外二是对结果做断言校验如 MultiGet 对状态码与值长度的检查确保基准度量的是正确行为。六、小结rocksdbjni-jmh为 RocksJava 提供了开箱即用的微基准测试框架mvn package一条命令即可产出包含全部依赖的 benchmarks uber-jarjava -jar即可运行测试本地 JNI 修改时通过mvn install:install-file安装 SNAPSHOT 并同步 pom.xml 版本即可无缝切换被测代码。工程内置的 Get、Put、MultiGet、Comparator 四组基准覆盖了读写 API 的多种数据载体byte[]与堆外ByteBuffer、多列族场景、批量读取与自定义比较器配置其Param参数矩阵可直接用于横向对比 Java 层不同调用方式的开销差异是评估 RocksJava 性能与验证 JNI 改动的实用工具。【免费下载链接】rocksdbA library that provides an embeddable, persistent key-value store for fast storage.项目地址: https://gitcode.com/gh_mirrors/ro/rocksdb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考