当前位置: 首页 > news >正文

Java Benchmark使用

如何测量Java代码的性能

在 Java 中,可以使用多种方法来测量一段代码的执行性能。使用 System.currentTimeMillis()是最常见的方法

long startTime = System.currentTimeMillis();// 需要测量的代码块
for (int i = 0; i < 1000000; i++) {// 示例代码
}long endTime = System.currentTimeMillis();
long duration = endTime - startTime; // 执行时间(毫秒)
System.out.println("Execution time: " + duration + " ms");

但是这么测结果是不太准确的,因为Java存在代码优化以及JIT编译等等,通常更准确的方式是使用Benchmark的方式来评估性能。

JMH基本使用

参考:https://github.com/openjdk/jmh
Java Microbenchmark Harness (JMH) 是一个用于基准测试 Java 代码的工具。它能够准确测量代码的性能,帮助开发者了解不同实现的效率。

首先添加依赖,jmh一般用于test,所以scope可以指定为test

<dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-core</artifactId><version>1.35</version>
</dependency>
<dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-generator-annprocess</artifactId><version>1.35</version>
</dependency>

示例如下:

package org.example;import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;import java.util.Arrays;
import java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class JMHExample {private int[] numbers;public static void main(String[] args) throws Exception {Options opts = new OptionsBuilder().include(JMHExample.class.getSimpleName()) // 指定测试的类.build();new Runner(opts).run();  // 运行}@Setup(Level.Trial)public void setup() {numbers = new int[1000];for (int i = 0; i < numbers.length; i++) {numbers[i] = i;}}@Benchmarkpublic int sumArray() {int sum = 0;for (int number : numbers) {sum += number;}return sum;}@Benchmarkpublic int sumArrayParallel() {return Arrays.stream(numbers).parallel().sum();}
}

在示例中,其实我们的目的就是比较两种对数组求和方式的性能,看看每秒的吞吐量如何,运行测试,可以在控制台得到相应输出。

常用注解

@Benchmark

@Benchmark 注解是用于标记测试方法的,类似 JUnit 中的 @Test 注解需要单元测试的方法一样,只有被这个注解标记的方法才会参与基准测试,且被标记的方法必须是 public 的。在一个基本测试类中至少包含一个被 @Benchmark 标记的方法,否则会抛出异常。

@BenchmarkMode

@BenchmarkMode 注解用于指定基准测试的模式。JMH 共有四种模式:

  1. Throughput:整体吞吐量,例如“1 秒内可以执行多少次调用”。
  2. AverageTime:调用的平均时间,例如“每次调用平均耗时 xxx 毫秒”。如果需要测试某个方法的平均耗时,可以使用@BenchmarkMode 注解并指定基准测试的模式为 AverageTime。
  3. SampleTime:随机取样,最后输出取样结果的分布,例如“99%的调用在 xxx 毫秒以内,99.99%的调用在 xxx 毫秒以内”。
  4. SingleShotTime:以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为 0,用于测试冷启动时的性能。
  5. All:所有的指标全算一遍

在使用时,@BenchmarkMode 注解可设置在类上也可以设置在基准方法上。例如:

@Benchmark
@BenchmarkMode(Mode.AverageTime)
public void methodToTest() {// 测试代码
}

@OutputTimeUnit

基准测试结果的时间类型。一般选择秒、毫秒、微秒,这里填入的是 TimeUnit 这个枚举类型,涉及单位很多从纳秒到天都有,按需选择,最终输出易读的结果。

@State

@State 指定了在类中变量的作用范围。@State 用于声明某个类是一个“状态”,可以用Scope 参数用来表示该状态的共享范围。这个注解必须加在类上,否则提示无法运行。它有三个取值。

  1. Benchmark:表示变量的作用范围是某个基准测试类。
  2. Thread:每个线程一份副本,如果配置了Threads注解,则每个Thread都拥有一份变量,它们互不影响。
  3. Group:联系上面的@Group注解,在同一个Group里,将会共享同一个变量实例。

本例中,相关变量的作用范围是 Benchmark。

@Warmup

预热,可以加在类上或者方法上,预热只是测试数据,是不作为测量结果的。

该注解一共有4个参数:

  1. iterations 预热阶段的迭代数
  2. time 每次预热时间
  3. timeUnit 时间单位,通常秒
  4. batchSize 批处理大小,指定每次操作调用几次方法

本例中,我们加在类上,让它迭代3次,每次1秒,时间单位秒。

@Setup

注解的作用就是我们需要在测试之前进行一些准备工作,比如对一些数据的初始化之类的,这个也和Junit的@Before等类似

@Teardown

在测试之后进行一些结束工作,主要用于资源回收

@Measurement

和预热类似,这个注解是会影响测试结果的,它的参数和 Warmup 一样,这里不多介绍。
本例中我们在迭代中设置的是5次,每次1秒。
通常 @Warmup 和 @Measurement 两个参数会一起使用。

@Fork

表示开启几个进程测试,通常我们设为1,如果数值大于1,则启用新的进程测试,如果设置为0,程序依然进行,但是在用户的 JVM 进程上运行。

@Threads

上面的注解注重开启几个进程,这里就是开启几个线程,只有一个参数 value,指定注解的value,将会开启并行测试,如果设置的 value 过大,如 Threads.Max,则使用处理机的相同线程数。

输出格式

public static void main(String[] args) throws RunnerException {Options opts = new OptionsBuilder()// 表示包含的测试类.include(JMHExample.class.getSimpleName()) // 最后结果输出文件的命名,不指定默认为jmh-reuslt.json.result("benchmark.json")// 结果输出什么格式,可以是json, csv, text等.resultFormat(ResultFormatType.JSON).build();new Runner(opts).run(); // 运行
}

作为程序开发人员,看懂测试结果没难度,测试结果文本能可视化更好。好在我们拿到了JMH 结果后,根据文件格式,我们可以二次加工,就可以图表化展示[2]。

JMH 支持的几种输出格式:

  1. TEXT 导出文本文件。
  2. CSV 导出csv格式文件。
  3. SCSV 导出scsv等格式的文件。
  4. JSON 导出成json文件。
  5. LATEX 导出到latex,一种基于ΤΕΧ的排版系统。

比如 CSV 格式的文件,我们就可以通过 EXCEL 处理获取图表,当然也还有其他的一些工具,例如:

  1. https://jmh.morethan.io/,参考:https://github.com/jzillmann/jmh-visualizer
    在这里插入图片描述
    这个网站要使用json格式的输出结果
    在这里插入图片描述

jmh-generator

JMH生成测试代码的方式有多种,例如jmh-generator-annprocessjmh-generator-reflection 是 JMH(Java Microbenchmark Harness)中用于基准测试生成的两个核心组件。

  1. jmh-generator-annprocess:这个模块用于在编译时处理基准测试的注解。它通过注解处理器生成相应的基准测试代码。
  • 性能:由于是在编译时生成代码,运行时的开销较小,因此性能较好。

  • 类型安全:在编译时生成的代码是类型安全的,可以捕获潜在的错误。

使用场景:适合于需要大量基准测试并且希望在编译时进行优化的场景。

  1. jmh-generator-reflection:这个模块使用反射机制在运行时生成基准测试代码,在运行时读取类的结构,并生成相应的基准测试实现。
  • 优点

    • 灵活性:可以动态生成基准测试,适合那些在编译时无法确定的基准测试场景。
    • 简化:对于简单或快速的基准测试,开发者无需进行额外的编译配置。
  • 缺点

    • 性能开销:由于使用反射,运行时性能开销相对较大。
    • 类型安全:可能导致运行时错误,缺乏编译时检查。

如果需要高性能、类型安全的基准测试,并且可以接受编译时的复杂性。选择 jmh-generator-annprocess,如果希望动态生成基准测试,或者在快速原型开发时需要更灵活的解决方案。选择 jmh-generator-reflection

对应的maven依赖如下

参考:https://mvnrepository.com/artifact/org.openjdk.jmh

<!-- 基于注解处理器生成 -->
<dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-generator-annprocess</artifactId><version>1.37</version><scope>test</scope>
</dependency>
<!-- 基于反射生成 -->
<dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-generator-reflection</artifactId><version>1.37</version><scope>test</scope>
</dependency>
<!-- 基于字节码生成 -->
<dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-generator-bytecode</artifactId><version>1.37</version><scope>test</scope>
</dependency>
http://www.jsqmd.com/news/40551/

相关文章:

  • 实用指南:12-机器学习与大模型开发数学教程-第1章1-4 导数与几何意义
  • 基于Vue社区共享游泳馆预约高效的系统n897q36e (工具+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • docker登录容器镜像仓库
  • 吴恩达深度学习课程二: 改善深层神经网络 第三周:超参数调整,批量标准化和编程框架(一)超参数调整
  • Go-秘籍-全-
  • Kotlin中的flow、stateflow、shareflow之间的区别和各自的功能 - 教程
  • 非离散网络流——P3347 [ZJOI2015] 醉熏熏的幻想乡
  • [note] 素数判定与分解质因数
  • 不能识别adb/usb口记录 - 实践
  • 恭喜自己,挑战成功! - Ghost
  • 如何在测试覆盖不足后补充验证
  • react动态表单
  • 完整教程:PDFBox - PDDocument 与 byte 数组、PDF 加密
  • Dark Side of the Moon
  • flask:自定义异常
  • 图片合集
  • OpenWrt路由的端口映射问题
  • 算法沉淀第七天(AtCoder Beginner Contest 428 和 小训练赛) - 详解
  • How-to-extract-text-from-PDF-Image-files-OCR-CarlZeng
  • Web应用模糊测试完全指南
  • 升鲜宝供应链管理系统、各端的访问地址及nginx 真实的配置方法
  • uiautomator2元素查看器WEditor的安装和启动
  • WEditor的使用方法
  • 【题解】LOJ6300. 「CodePlus 2018 3 月赛」博弈论与概率统计
  • 感情粉末沿着试管边缘 在祝福中逐渐分解 加热认知离子重新排列 于底部悲伤沉淀
  • C#循序渐进 - 详解
  • 2025.11.14 - A
  • 从RvmTranslator到PlantAssistant
  • MI50 在ubuntu 下 风扇控制实现
  • PortSwigger靶场之 CSRF where token is not tied to user session通关秘籍 - 实践