【开发篇】十七、基准测试框架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

相关文章

GSON框架下将百度天气JSON数据转JavaBean

《GSON框架下将百度天气JSON数据转JavaBean》这篇文章主要为大家详细介绍了如何在GSON框架下实现将百度天气JSON数据转JavaBean,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录前言一、百度天气jsON1、请求参数2、返回参数3、属性映射二、GSON属性映射实战1、类对象映

Python实战之SEO优化自动化工具开发指南

《Python实战之SEO优化自动化工具开发指南》在数字化营销时代,搜索引擎优化(SEO)已成为网站获取流量的重要手段,本文将带您使用Python开发一套完整的SEO自动化工具,需要的可以了解下... 目录前言项目概述技术栈选择核心模块实现1. 关键词研究模块2. 网站技术seo检测模块3. 内容优化分析模

基于Java开发一个极简版敏感词检测工具

《基于Java开发一个极简版敏感词检测工具》这篇文章主要为大家详细介绍了如何基于Java开发一个极简版敏感词检测工具,文中的示例代码简洁易懂,感兴趣的小伙伴可以跟随小编一起学习一下... 目录你是否还在为敏感词检测头疼一、极简版Java敏感词检测工具的3大核心优势1.1 优势1:DFA算法驱动,效率提升10

Python开发简易网络服务器的示例详解(新手入门)

《Python开发简易网络服务器的示例详解(新手入门)》网络服务器是互联网基础设施的核心组件,它本质上是一个持续运行的程序,负责监听特定端口,本文将使用Python开发一个简单的网络服务器,感兴趣的小... 目录网络服务器基础概念python内置服务器模块1. HTTP服务器模块2. Socket服务器模块

解决若依微服务框架启动报错的问题

《解决若依微服务框架启动报错的问题》Invalidboundstatement错误通常由MyBatis映射文件未正确加载或Nacos配置未读取导致,需检查XML的namespace与方法ID是否匹配,... 目录ruoyi-system模块报错报错详情nacos文件目录总结ruoyi-systnGLNYpe

Java 与 LibreOffice 集成开发指南(环境搭建及代码示例)

《Java与LibreOffice集成开发指南(环境搭建及代码示例)》本文介绍Java与LibreOffice的集成方法,涵盖环境配置、API调用、文档转换、UNO桥接及REST接口等技术,提供... 目录1. 引言2. 环境搭建2.1 安装 LibreOffice2.2 配置 Java 开发环境2.3 配

基于Python Playwright进行前端性能测试的脚本实现

《基于PythonPlaywright进行前端性能测试的脚本实现》在当今Web应用开发中,性能优化是提升用户体验的关键因素之一,本文将介绍如何使用Playwright构建一个自动化性能测试工具,希望... 目录引言工具概述整体架构核心实现解析1. 浏览器初始化2. 性能数据收集3. 资源分析4. 关键性能指

Python38个游戏开发库整理汇总

《Python38个游戏开发库整理汇总》文章介绍了多种Python游戏开发库,涵盖2D/3D游戏开发、多人游戏框架及视觉小说引擎,适合不同需求的开发者入门,强调跨平台支持与易用性,并鼓励读者交流反馈以... 目录PyGameCocos2dPySoyPyOgrepygletPanda3DBlenderFife

使用Python开发一个Ditto剪贴板数据导出工具

《使用Python开发一个Ditto剪贴板数据导出工具》在日常工作中,我们经常需要处理大量的剪贴板数据,下面将介绍如何使用Python的wxPython库开发一个图形化工具,实现从Ditto数据库中读... 目录前言运行结果项目需求分析技术选型核心功能实现1. Ditto数据库结构分析2. 数据库自动定位3

Django开发时如何避免频繁发送短信验证码(python图文代码)

《Django开发时如何避免频繁发送短信验证码(python图文代码)》Django开发时,为防止频繁发送验证码,后端需用Redis限制请求频率,结合管道技术提升效率,通过生产者消费者模式解耦业务逻辑... 目录避免频繁发送 验证码1. www.chinasem.cn避免频繁发送 验证码逻辑分析2. 避免频繁