Java中如何计算执行时间?为何相同示例多次运行结果存在差异?
为什么同一算法的执行时间差异明显?怎么精准测量?
这是性能测试里非常常见的问题,我来帮你理清楚原因和靠谱的解决办法~
为什么单次执行时间波动大?
你遇到的23-29ms的波动,主要是这些因素导致的:
- 操作系统线程调度:你的Java进程随时可能被操作系统暂停,让给其他后台进程(比如杀毒软件、系统更新、浏览器等),这部分等待时间会被算进你的计时里,每次暂停的时长都不一样。
- JVM即时编译(JIT):Java代码一开始是解释执行的,JVM会把频繁执行的热点代码编译成机器码(这个过程叫JIT编译)。前几次执行
bm.search时可能还没完成编译,速度慢;后面编译完成后速度会提升,时间自然就不一样了。 - CPU缓存效应:第一次执行时,你的测试文本和模式串可能还不在CPU的高速缓存里,需要从内存加载,速度慢;后面再执行时数据已经在缓存里,访问速度快很多,时间就变短了。
- 其他系统干扰:比如后台突然跑个磁盘IO任务、网络请求,都会抢占系统资源,影响你的程序执行速度。
更精准可靠的计算方法
针对这些问题,有几个实用的解决方案:
1. 先预热代码,再多次执行取统计值
自己写代码的话,先执行几次搜索让JVM完成JIT编译和缓存加载,然后执行几十上百次,取平均值或者中位数(中位数更能避免极端值的影响):
import java.util.Arrays; // 预热:先执行10次,让JVM完成编译和缓存加载 for (int i = 0; i < 10; i++) { bm.search(mediumtext, mediumpattern); } // 正式测试,执行100次 int totalRuns = 100; long totalNanos = 0; long[] times = new long[totalRuns]; for (int i = 0; i < totalRuns; i++) { long start = System.nanoTime(); bm.search(mediumtext, mediumpattern); long end = System.nanoTime(); long elapsed = end - start; times[i] = elapsed; totalNanos += elapsed; } // 计算平均值 double avgMs = totalNanos / (1_000_000.0 * totalRuns); // 计算中位数(简单实现) Arrays.sort(times); double medianMs = times[totalRuns / 2] / 1_000_000.0; System.out.printf("Average time: %.3f ms\n", avgMs); System.out.printf("Median time: %.3f ms\n", medianMs);
2. 使用专业的微基准测试框架:JMH
如果要做更严谨的性能测试,推荐用OpenJDK的JMH(Java Microbenchmark Harness),它专门为Java微基准测试设计,自动处理预热、避免死码消除、控制执行环境,结果非常可靠。
举个简单的JMH示例:
import org.openjdk.jmh.annotations.*; import java.util.concurrent.TimeUnit; // 指定测试模式为平均时间,输出单位为毫秒 @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) // 预热5轮,每轮1秒 @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 正式测试10轮,每轮1秒 @Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS) // fork一个新的JVM进程执行测试,避免其他代码干扰 @Fork(1) public class BMSearchBenchmark { // 提前初始化测试数据,避免每次测试都重复创建 private static final String MEDIUM_TEXT = "你的测试文本内容"; private static final String MEDIUM_PATTERN = "你的模式串内容"; private static final BM BM_INSTANCE = new BM(); // 被测试的方法,JMH会自动调用这个方法进行测试 @Benchmark public void testBmSearch() { BM_INSTANCE.search(MEDIUM_TEXT, MEDIUM_PATTERN); } }
使用JMH需要在项目里添加对应依赖(比如Maven/Gradle),然后通过JMH插件运行,它会输出详细的统计结果,包括平均值、误差范围等,比手动实现的测试严谨得多。
内容的提问来源于stack exchange,提问作者user14388485
相关产品推荐
相关产品推荐

