Java Benchmark使用

news/2025/11/14 21:00:32/文章来源:https://www.cnblogs.com/vonlinee/p/19223416

如何测量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.mzph.cn/news/965708.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实用指南:12-机器学习与大模型开发数学教程-第1章1-4 导数与几何意义

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

基于Vue社区共享游泳馆预约高效的系统n897q36e (工具+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

docker登录容器镜像仓库

容器镜像仓库就是我们平时自己构建的镜像有一个存储的位置,方便自己平时进行拉取,测试用的我使用的是ucloud这容器仓库ucloud.cn登录容器仓库的操作docker logindocker login uhub.service.ucloud.cn# username 为登…

吴恩达深度学习课程二: 改善深层神经网络 第三周:超参数调整,批量标准化和编程框架(一)超参数调整

此分类用于记录吴恩达深度学习课程的学习笔记。 课程相关信息链接如下:原课程视频链接:[双语字幕]吴恩达深度学习deeplearning.ai github课程资料,含课件与笔记:吴恩达深度学习教学资料 课程配套练习(中英)与答案…

Go-秘籍-全-

Go 秘籍(全)原文:zh.annas-archive.org/md5/d17f8ead62b31a6ec2bbef4005dc3b6d 译者:飞龙 协议:CC BY-NC-SA 4.0第一章:错误处理技巧 1.0 引言 亚历山大蒲柏在他的论批评的散文中写道:“出错是人性的”。由于软…

Kotlin中的flow、stateflow、shareflow之间的区别和各自的功能 - 教程

Kotlin中的flow、stateflow、shareflow之间的区别和各自的功能 - 教程2025-11-14 20:36 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto…

非离散网络流——P3347 [ZJOI2015] 醉熏熏的幻想乡

非离散网络流——P3347 [ZJOI2015] 醉熏熏的幻想乡 观察费用为 \(a_ix^2+b_ix\),如果是离散的,则可以套路的建边 \(a_i+b_i,3a_i+b_i,5a_i+b_i,\dots\),可本题 \(x\in R\)。 于是连续意义下我们应该求导得到 \(2a_i…

[note] 素数判定与分解质因数

在某些毒瘤的数论题中,可能出现对 \(10^{18}\) 的范围内的数质因数分解的情况。这时,可以使用 Fermat 和 Miller-Rabin 算法进行素性判定,Pollard-Rho 算法寻找非平凡因子,两者结合以快速质因数分解。 Fermat 素性…

不能识别adb/usb口记录 - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

恭喜自己,挑战成功! - Ghost

恭喜自己,挑战成功! 我终于,拿省一啦! 正文: 在2025年8月18日,本人开始了一项挑战 挑战三个月达省一 在三个月后,2025年11月14日,NOI官网发布了分数and分数线 本人以高出一等分数线10分的分数(250pts),成功…

如何在测试覆盖不足后补充验证

测试覆盖不足是项目质量的重大隐患,一旦发现(尤其是当它已导致线上问题时),团队必须立即采取系统性的补充验证措施。核心策略是停止盲目开发,转而执行一套以风险为导向的补救流程。 首先,必须立即对未覆盖的区域…

react动态表单

来个例子 比如有两种登录方式 // 导入表单验证库 import * as z from zod; // 导入消息提示组件 import { toast } from sonner; import { useState } from react; // 导入 UI 组件 import { Input } from @repo/shadc…

完整教程:PDFBox - PDDocument 与 byte 数组、PDF 加密

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Dark Side of the Moon

“Speak to Me” "Ive been mad for fucking years, absolutely years, been over the edge for years, been working me buns off for bands..." "Ive always been mad, I know Ive been mad, like mo…

flask:自定义异常

一,代码: 自定义异常:class ApiError(Exception):""""API接口异常错误"""messsage = ""# 默认错误码status_code = 400# 自定义一个return_code, 作为更细粒的错误代码…

图片合集

\(sin(2\theta)=2sin(\theta)cos(\theta)=\frac{2tan(\theta)}{1+tan^2(\theta)}\) \(cos(2\theta)=cos^2(\theta)-sin^2(\theta)=\frac{1-tan^2(\theta)}{1+tan^2(\theta)}\) \(tan(2\theta)=\frac{2tan(\theta)}{1-t…

升幂引理(LTE)

记 \(\nu_p(n)\) 表示 \(n\) 的标准分解中素数 \(p\) 的幂次,即 \(p^{\nu_p(n)} \parallel n\)。 该引理分为两部分:设 \(a, \, b\) 为不等正整数且 \(p \mid a - b\),\((p, \, ab) = 1\),若 \(p\) 为奇素数,则 \(…

OpenWrt路由的端口映射问题

之前做过AI大模型搭建,想将搭建的访问能开放到外部局域网,方便其他人访问,需要一些网络设置,就在这个网络配置上踩了些坑。 想在最外局域网中访问,但是自己这边用了两级的路由,第一级是小米的无线路由,以及上一…

解码IPC-管道与信号

进程间通信(IPC) 进程间通信(Inter Process Communication,简称 IPC)是进程间的信息交换,核心目的包括数据传输、共享资源、控制进程,方便对进程的管理与调度。常见 IPC 方式有管道通信、信号通信、共享内存、消…

算法沉淀第七天(AtCoder Beginner Contest 428 和 小训练赛) - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …