
在本书第二部分,我们探讨了基于互联网通信协议(如HTTP、WebSocket、Kafka)的性能测试,通常通过在施压端启动客户端、发起请求、解析响应,分析全链路性能瓶颈并优化。然而,这种方法覆盖整个请求链路,涉及多个方法和代码片段,难以精准定位性能问题。是否存在一种方法,能直接测试特定代码片段的性能,帮助测试工程师快速识别代码中的“坏味道”?
答案是肯定的!本章的主角——微基准测试(Microbenchmarking)应运而生。微基准测试专注于评估程序中特定代码片段或方法的性能,通过对比不同实现方式的性能差异,助力开发者选择最优方案,优化软件性能。本章以超市八的场景为例,结合Java微基准测试工具JMH,展示如何通过微基准测试识别性能瓶颈,提升系统效率。
微基准测试是一种针对程序中特定代码片段的性能测试方法,旨在量化其在不同场景下的性能指标(如吞吐量、执行耗时、CPU占用、内存占用),为性能优化提供数据支持。与全链路测试相比,微基准测试聚焦于单一方法或代码块,测试粒度更细,适合快速定位瓶颈。例如,在超市八的埋点系统中,测试人员可通过微基准测试比较不同序列化方法(如JSON vs Protobuf)的性能,优化数据处理效率。
微基准测试的核心优势包括:
UserBehavior对象的序列化方法,找出性能瓶颈。然而,微基准测试需谨慎设计,避免常见误区。例如,使用System.currentTimeMillis()测量代码耗时精度不足,且受JVM优化(如JIT编译)、垃圾回收(GC)等影响,结果不准确。以下代码展示了常见的错误做法:
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)成为行业标准,提供高精度、可重复的测试环境。
JMH是专门为Java代码性能测试设计的开源工具,广泛用于微基准测试。其核心优势包括:
UserBehavior序列化方法的性能。JMH并非Java SDK内置工具,需通过Maven引入依赖。以下是配置(版本1.33,兼容性高,若需升级可参考官方文档):
<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>在超市八的埋点系统中,开发团队为确保线程安全,考虑在UserBehavior计数方法上添加synchronized关键字,但担心其性能开销。本节通过JMH测试两个方法:safe()(带synchronized)和notSafe()(不带synchronized),比较其吞吐量差异,验证同步锁的影响。
以下是测试用例,包含JMH注解和关键配置说明:
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运行时生成详细日志,分为五类:
测试结果(摘自日志):
# 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倍,表明锁竞争开销显著。在超市八的埋点系统中,微基准测试可用于优化关键代码。例如:
UserBehavior的JSON序列化(FastJSON)与Protobuf序列化的耗时,优化数据处理效率。@Threads(8),模拟高并发,验证synchronized在多线程下的性能影响。@Warmup和@Measurement的迭代次数(如5次),提高结果稳定性。通过JMH,测试人员可精准定位性能瓶颈,为超市八的埋点系统选择最优实现方案。下一节将结合超市八的场景,展示如何通过JMH测试UserBehavior序列化和加密方法,优化系统性能,确保千万级用户的稳定体验。