首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【连载 72】微基准测试与 JMH 实战

【连载 72】微基准测试与 JMH 实战

作者头像
FunTester
发布2025-07-16 15:58:52
发布2025-07-16 15:58:52
4370
举报
文章被收录于专栏:FunTesterFunTester

在本书第二部分,我们探讨了基于互联网通信协议(如HTTP、WebSocket、Kafka)的性能测试,通常通过在施压端启动客户端、发起请求、解析响应,分析全链路性能瓶颈并优化。然而,这种方法覆盖整个请求链路,涉及多个方法和代码片段,难以精准定位性能问题。是否存在一种方法,能直接测试特定代码片段的性能,帮助测试工程师快速识别代码中的“坏味道”?

答案是肯定的!本章的主角——微基准测试(Microbenchmarking)应运而生。微基准测试专注于评估程序中特定代码片段或方法的性能,通过对比不同实现方式的性能差异,助力开发者选择最优方案,优化软件性能。本章以超市八的场景为例,结合Java微基准测试工具JMH,展示如何通过微基准测试识别性能瓶颈,提升系统效率。

11.1 微基准测试基础

微基准测试是一种针对程序中特定代码片段的性能测试方法,旨在量化其在不同场景下的性能指标(如吞吐量、执行耗时、CPU占用、内存占用),为性能优化提供数据支持。与全链路测试相比,微基准测试聚焦于单一方法或代码块,测试粒度更细,适合快速定位瓶颈。例如,在超市八的埋点系统中,测试人员可通过微基准测试比较不同序列化方法(如JSON vs Protobuf)的性能,优化数据处理效率。

微基准测试的核心优势包括:

  • • 精准定位:直接测试特定方法,排除外部因素(如网络延迟、数据库响应)的干扰。例如,测试UserBehavior对象的序列化方法,找出性能瓶颈。
  • • 性能对比:通过量化指标,比较同一功能的多种实现方式。例如,比较超市八埋点系统中不同加密算法的耗时,选择最优方案。
  • • 回归测试:在代码变更时,定期运行微基准测试,检测性能退化。例如,超市八升级Kafka生产者代码后,可通过微基准测试验证性能是否下降。
  • • 系统稳定性:通过持续监控关键方法的性能,及时发现潜在风险。例如,定期测试订单查询SQL的执行耗时,预防数据库性能问题。

然而,微基准测试需谨慎设计,避免常见误区。例如,使用System.currentTimeMillis()测量代码耗时精度不足,且受JVM优化(如JIT编译)、垃圾回收(GC)等影响,结果不准确。以下代码展示了常见的错误做法:

代码语言:javascript
复制
long start = System.currentTimeMillis();
for (int i = ; i < ; i++) {
    // do nothing
}
long end = System.currentTimeMillis();
System.out.println("执行耗时: " + (end - start) + " 毫秒");

上述代码无法保证时间精度,且JVM的动态优化(如循环展开)可能导致结果波动。为解决这些问题,Java微基准测试工具JMH(Java Microbenchmark Harness)成为行业标准,提供高精度、可重复的测试环境。

11.1 Java微基准测试工具JMH

JMH是专门为Java代码性能测试设计的开源工具,广泛用于微基准测试。其核心优势包括:

  • • 易上手:JMH提供简洁的API,官方文档和社区支持丰富,适合Java开发者和测试工程师快速学习。例如,超市八的测试团队可通过JMH快速测试UserBehavior序列化方法的性能。
  • • 高精度与可重复性:JMH通过预热(Warmup)、多次迭代和JVM状态控制,消除JIT编译、GC等干扰,确保结果准确。例如,测试订单加密方法时,JMH可提供纳秒级精度。
  • • 功能丰富:支持多线程测试、垃圾回收统计、JVM参数配置,并与JUnit、Maven等集成,适配CI/CD流程。例如,超市八可将JMH测试嵌入Jenkins,自动化监控代码性能。

11.1.1 JMH依赖与配置

JMH并非Java SDK内置工具,需通过Maven引入依赖。以下是配置(版本1.33,兼容性高,若需升级可参考官方文档):

代码语言:javascript
复制
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.33</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.33</version>
    <scope>provided</scope>
</dependency>

11.1.2 JMH演示:同步方法性能测试

在超市八的埋点系统中,开发团队为确保线程安全,考虑在UserBehavior计数方法上添加synchronized关键字,但担心其性能开销。本节通过JMH测试两个方法:safe()(带synchronized)和notSafe()(不带synchronized),比较其吞吐量差异,验证同步锁的影响。

以下是测试用例,包含JMH注解和关键配置说明:

代码语言:javascript
复制
package org.funtester.performance.books.chapter11.section1;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.Throughput)// 测试模式:吞吐量(每秒操作数)
@State(Scope.Thread)// 每个线程一个实例,隔离状态
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)// 预热1次,每次1秒
@Measurement(iterations = 1, batchSize = 1)// 测量1次,批次大小1
@Threads(1)// 单线程测试
@Fork(value = 3, jvmArgs = {"-Xms1G", "-Xmx1G"})// Fork 3个进程,设置JVM内存
@OutputTimeUnit(TimeUnit.MICROSECONDS)// 输出单位:微秒
publicclassSyncTest {

    inti=; // 共享变量

    @Benchmark
    publicvoidsafeTest() {
        safe(); // 测试线程安全方法
    }

    @Benchmark
    publicvoidnotSafeTest() {
        notSafe(); // 测试非线程安全方法
    }

    publicsynchronizedvoidsafe() {
        i++; // 线程安全计数
    }

    publicvoidnotSafe() {
        i++; // 非线程安全计数
    }

    publicstaticvoidmain(String[] args)throws RunnerException {
        Optionsoptions=newOptionsBuilder()
                .include(SyncTest.class.getSimpleName()) // 指定测试类
                .result("result.json") // 输出结果到文件
                .resultFormat(ResultFormatType.JSON) // 结果格式为JSON
                .build();
        newRunner(options).run(); // 运行测试
    }
}

JMH注解说明

  • • @BenchmarkMode(Mode.Throughput):测试吞吐量(ops/s),其他模式包括AverageTime(平均耗时)、SampleTime(采样时间)。
  • • @State(Scope.Thread):每个线程独立状态,避免并发干扰。
  • • @Warmup:预热1次,每次1秒,消除JIT编译影响。
  • • @Measurement:测量1次,批次大小1,确保精准测试。
  • • @Threads(1):单线程测试,简化分析。
  • • @Fork:Fork 3个进程,设置JVM内存为1GB,减少环境干扰。
  • • @OutputTimeUnit:结果以微秒为单位,便于比较。

运行日志:JMH运行时生成详细日志,分为五类:

  1. 1. Setup:初始化测试环境,如加载类、初始化状态。
  2. 2. Warmup:预热阶段,运行多次以稳定JVM状态。
  3. 3. Measurement:测量阶段,记录性能数据。
  4. 4. Result:输出测试结果,如吞吐量统计。
  5. 5. TearDown:清理资源,释放状态。

测试结果(摘自日志):

代码语言:javascript
复制
# Run complete. Total time: 00:00:23

Benchmark              Mode  Cnt     Score   Error   Units
SyncTest.notSafeTest  thrpt       2689.625          ops/us
SyncTest.safeTest     thrpt        366.672          ops/us

Benchmark result is saved to result.json

结果分析

  • • notSafeTest吞吐量为2689.625 ops/μs(约26.9亿次/秒),safeTest为366.672 ops/μs(约3.67亿次/秒),synchronized导致性能下降约7倍,表明锁竞争开销显著。
  • • 尽管性能下降明显,但绝对值仍达亿级,说明单线程场景下影响可控。在超市八的高并发场景中,需结合多线程测试进一步验证。

11.1.3 性能测试应用

在超市八的埋点系统中,微基准测试可用于优化关键代码。例如:

  1. 1. 序列化性能:比较UserBehavior的JSON序列化(FastJSON)与Protobuf序列化的耗时,优化数据处理效率。
  2. 2. 多线程测试:修改@Threads(8),模拟高并发,验证synchronized在多线程下的性能影响。
  3. 3. 参数调优:调整@Warmup@Measurement的迭代次数(如5次),提高结果稳定性。
  4. 4. 回归测试:在代码变更后运行JMH测试,监控性能退化。例如,超市八升级加密算法后,比较新旧版本的耗时。

通过JMH,测试人员可精准定位性能瓶颈,为超市八的埋点系统选择最优实现方案。下一节将结合超市八的场景,展示如何通过JMH测试UserBehavior序列化和加密方法,优化系统性能,确保千万级用户的稳定体验。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-07-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FunTester 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 11.1 微基准测试基础
  • 11.1 Java微基准测试工具JMH
    • 11.1.1 JMH依赖与配置
    • 11.1.2 JMH演示:同步方法性能测试
    • 11.1.3 性能测试应用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档