【开发篇】十七、基准测试框架JMH

2024-04-12 03:20

本文主要是介绍【开发篇】十七、基准测试框架JMH,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1、JMH
  • 2、运行方式二
  • 3、死代码与黑洞变量
  • 4、可视化分析
  • 5、案例:日期格式化方法性能测试
  • 6、总结
  • 7、整合到SpringBoot

判断一个方法的耗时 ⇒ endTime-startTime ⇒ 不准确,首先部分对象懒加载,第一次请求会慢一些,其次,程序运行时,JIT即时编译器会实时优化代码,如随着执行次数的增加,程序性能逐渐优化:

在这里插入图片描述

⇒ JMH(Java Microbenchmark Harness)准确的测方法执行的性能

1、JMH

  • https://github.com/openjdk/jmh
  • JMH会先执行预热,确保JIT队代码优化之后再进行测试

在这里插入图片描述

  • JMH项目搭建,新建个空目录,执行mvn生成模块
 mvn archetype:generate \
-DinteractiveMode=false \
-DarchetypeGroupId=org.openjdk.jmh \
-DarchetypeArtifactId=jmh-java-benchmark-archetype \
-DgroupId=org.sample \
-DartifactId=test \
-Dversion=1.0

在这里插入图片描述
在这里插入图片描述

  • 写一个简单案例
//执行5轮预热,每次持续1秒
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
//启动多少个进程 + 追加JMV参数
@Fork(value = 1, jvmArgsAppend = {"-Xms1g", "-Xmx1g"})
//指定显示平均时间,单位纳秒。Mode.ALL显示所有数据
@BenchmarkMode(Mode.AverageTime)
//纳秒
@OutputTimeUnit(TimeUnit.NANOSECONDS)
//变量共享的范围,对象在单个线程中共享就选Scope.Thread
@State(Scope.Benchmark)
public class MyBenchmark {@Benchmarkpublic int test1() {int i = 0;i++;return i;}}
  • mvn verfiy校验并生成jar包

在这里插入图片描述

  • 运行服务,得到结果

在这里插入图片描述
在这里插入图片描述

2、运行方式二

每次打jar包运行很麻烦,改用一个main方法:

//执行5轮预热,每次持续1秒
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
//启动多少个进程 + 追加JMV参数
@Fork(value = 1, jvmArgsAppend = {"-Xms1g", "-Xmx1g"})
//指定显示平均时间,单位纳秒。Mode.ALL显示所有数据
@BenchmarkMode(Mode.AverageTime)
//纳秒
@OutputTimeUnit(TimeUnit.NANOSECONDS)
//变量共享的范围,对象在单个线程中共享就选Scope.Thread
@State(Scope.Benchmark)
public class MyBenchmark {@Benchmarkpublic int test1() {int i = 0;i++;return i;}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder()//测哪个类.include(MyBenchmark.class.getSimpleName())//结果的格式,json文件//.resultFormat(ResultFormatType.JSON)//进程数.forks(1).build();new Runner(opt).run();}
}

结果相比jar包运行的方式略有出入,因为IDEA启动时自身会设置一些参数(所以要准确就建议用jar的方式)

在这里插入图片描述

3、死代码与黑洞变量

如下,定义的变量i,最后没有返回,JIT会认为i没有使用,会把这一段代码自动忽略掉

//...
public void testMethod() {int i = 0;i++;}
//...

此时,JMH返回的结果明显下降很多:

在这里插入图片描述

以上即死代码,JMH测试过程中,可用一个黑洞的变量,调用consume消费方法,就可以避免JIT把这些变量自动删除掉:

public void testMethod(Blackhole blackhole) {int i= 0;i++;int j = 0;j++;//使用黑洞,避免i,j变量没有使用被JIT当成死代码优化时去掉blackhole.consume(i);blackhole.consume(j);
}

4、可视化分析

  • 生成json格式
//....resultFormat(ResultFormatType.JSON)
  • 上传到:https://jmh.morethan.io/

在这里插入图片描述

5、案例:日期格式化方法性能测试

package org.sample;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.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.concurrent.TimeUnit;//执行5轮预热,每次持续1秒
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
//执行一次测试
@Fork(value = 1, jvmArgsAppend = {"-Xms1g", "-Xmx1g"})
//显示平均时间,单位纳秒
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class DateBench {private static String sDateFormatString = "yyyy-MM-dd HH:mm:ss";private Date date = new Date();private LocalDateTime localDateTime = LocalDateTime.now();//优化private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal();private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");@Setuppublic void setUp() {SimpleDateFormat sdf = new SimpleDateFormat(sDateFormatString);simpleDateFormatThreadLocal.set(sdf);}//测试Date@Benchmarkpublic String date() {SimpleDateFormat simpleDateFormat = new SimpleDateFormat(sDateFormatString);return simpleDateFormat.format(date);}//测试LocalDateTime@Benchmarkpublic String localDateTimeNotSave() {return localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));}/*** 优化测试Date的方法* SimpleDateFormat线程不安全,用ThreadLocal优化* 避免每次执行都创建一个SimpleDateFormat对象*/@Benchmarkpublic String dateThreadLocal() {return simpleDateFormatThreadLocal.get().format(date);}/*** 优化LocalDateTime*/@Benchmarkpublic String localDateTime() {return localDateTime.format(formatter);}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(DateBench.class.getSimpleName()).resultFormat(ResultFormatType.JSON).forks(1).build();new Runner(opt).run();}
}

结果:

在这里插入图片描述

结论:

  • Date对象使用的SimpleDateFormat线程不安全,每次需要重新创建对象,或者将对象放入ThreadLocal,后者性能好一点
  • LocalDateTime对象使用的DateTimeFormatter线程安全,且性能好,将DateTimeFormatter对象保存一下,别每次都创建,性能会更好

6、总结

Jmh可以完全模拟运行环境中的Java虚拟机参数,同时支持预热能通过JIT执行优化后的代码获得更为准确的数据

7、整合到SpringBoot

<dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-core</artifactId><version>${jmh.version}</version><scope>test</scope>
</dependency><dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-generator-annprocess</artifactId><version>${jmh.version}</version><scope>test</scope>
</dependency>

和之前单独一个jmh项目相比,整合时,可在UT中完成,setup初始是从IOC中获取要测试的方法所在类的Bean。且每次执行Benchmark注解的方法,都会运行一个新的SpringBoot服务,下面同时测5个方法,因此需要在配置文件中将端口设置成随机生成的。

import org.junit.jupiter.api.Test;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
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.OptionsBuilder;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;import java.io.IOException;
import java.util.concurrent.TimeUnit;//执行5轮预热,每次持续1秒
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
//执行一次测试
@Fork(value = 1, jvmArgsAppend = {"-Xms1g", "-Xmx1g"})
//显示平均时间,单位纳秒
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class PracticeBenchmarkTest {private UserController userController;private ApplicationContext context;//初始化将springboot容器启动 端口号随机@Setuppublic void setup() {this.context = new SpringApplication(JvmOptimizeApplication.class).run();userController = this.context.getBean(UserController.class);}//启动这个测试用例进行测试@Testpublic void executeJmhRunner() throws RunnerException, IOException {new Runner(new OptionsBuilder().shouldDoGC(true).forks(0).resultFormat(ResultFormatType.JSON).shouldFailOnError(true).build()).run();}//用黑洞消费数据,避免JIT消除代码@Benchmarkpublic void test1(final Blackhole bh) {bh.consume(userController.user1());}@Benchmarkpublic void test2(final Blackhole bh) {bh.consume(userController.user2());}@Benchmarkpublic void test3(final Blackhole bh) {bh.consume(userController.user3());}@Benchmarkpublic void test4(final Blackhole bh) {bh.consume(userController.user4());}@Benchmarkpublic void test5(final Blackhole bh) {bh.consume(userController.user5());}
}

随机端口:

server:
#  port: 8882port: ${random.int(2000,8000)}		#!!!!tomcat:threads:min-spare: 50max: 500

这篇关于【开发篇】十七、基准测试框架JMH的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/895963

相关文章

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

Python基于wxPython和FFmpeg开发一个视频标签工具

《Python基于wxPython和FFmpeg开发一个视频标签工具》在当今数字媒体时代,视频内容的管理和标记变得越来越重要,无论是研究人员需要对实验视频进行时间点标记,还是个人用户希望对家庭视频进行... 目录引言1. 应用概述2. 技术栈分析2.1 核心库和模块2.2 wxpython作为GUI选择的优

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应

Python GUI框架中的PyQt详解

《PythonGUI框架中的PyQt详解》PyQt是Python语言中最强大且广泛应用的GUI框架之一,基于Qt库的Python绑定实现,本文将深入解析PyQt的核心模块,并通过代码示例展示其应用场... 目录一、PyQt核心模块概览二、核心模块详解与示例1. QtCore - 核心基础模块2. QtWid