橘子学JDK之JMH-01(入门)

一、前言

清明节在家的时候,有个老弟在一个群里看到一段代码。

package com.cache.mycache;

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.util.HashMap;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@BenchmarkMode({Mode.Throughput})
@State(value = Scope.Benchmark)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class ConcurrentSetJMH {
    private static final int SIZE = (2 << 14);
    private static final HashMap<Integer, Item> map = new HashMap<>();

    public ConcurrentSetJMH() {
        for (int i = 0; i < SIZE; i++) {
            map.put(i, new Item(i));
        }
    }

    @Benchmark
    @Threads(32)
    public void sameKey(ThreadState threadState) {
        threadState.sameItem.setValue(threadState.value);
    }

    @Benchmark
    @Threads(32)
    public void spreadKey(ThreadState threadState) {
        threadState.spreadItem.setValue(threadState.value);
    }

    @State(Scope.Thread)
    public static class ThreadState {
        static final Random random = new Random();
        int value = random.nextInt();
        int index = value & (SIZE - 1);
        Item spreadItem = map.get(index);
        Item sameItem = map.get(SIZE / 2);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(ConcurrentSetJMH.class.getSimpleName())
                .result("ComputeBenchMark.json")
                .resultFormat(ResultFormatType.JSON).build();

        new Runner(opt).run();
    }

    private static final class Item {
        private int value;

        Item(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }

        public void setValue(int value) {
            this.value = value;
        }
    }
}

老弟大呼,这是什么玩意,为什么我没见过这样的代码,我不会咋办啊。橘子哥,你知道这是啥吗。我安抚了一下他焦虑的内心,告诉他,别慌,JMH而已,不是什么黑科技。他说他想学这个。我突然想起,我还有个博客,好像还有个JDK的专栏,那就写一点这玩意的用法吧。

二、关于测试

JMH实际上能用到的地方很多,我先说一个学JMH常见的开场白,大家在实际开发中遇到自己优化了一段代码的时候一般都想测一下这段代码的运行效率如何,和旧的代码相比优化效率点在哪里。
一般情况下,我们都会很自然的写一段main函数,然后在代码开始输出当前时间,然后在代码结束之后输出当前时间,两个时间一做减法,得到程序运行时间,最后一比较哦,优化了,有时候明明优化了但是看时间反而长了。
这种方法,有点小low。
其实也不是low,有时候简单的测一下完全可以,但是很多时候,这种方式不对。因为java这玩意,jvm有个JIT的编译器,这玩意是运行时优化的,有的程序在被JIT判定为热点代码的时候,是会拿出来单独优化,然后性能得以提升。你这种直接上去测,压根走不到JIT,自然也就不能真实模拟了。
其次,你得这种方式,每次都跑一次,CPU利用不到真实场合。总之就是各种指标他都对不上。导致你这个测试,不准确。
所以就有了JMH这个东西。

三、关于JMH

我们先来看一下他的官网介绍。
JMH官方地址

官方文档开篇明义:
全称为:Java Microbenchmark Harness
JMH is a Java harness for building, running, and analysing nano/micro/milli/macro benchmarks written in Java and other languages targeting the JVM.
翻译一下就是: JMH 是一个 Java 工具,用于构建、运行和分析用 Java 和其他针对 JVM 的语言编写的纳/微米/毫/宏观基准测试。

何谓 Micro Benchmark 呢?简单地说就是在 method 层面上的 benchmark,精度可以精确到微秒级。

# Java 的基准测试需要注意的几个点:
	测试前需要预热
	防止无用代码进入测试方法中
	并发测试
	测试结果呈现

# JMH 的使用场景:
	定量分析某个热点函数的优化效果
	想定量地知道某个函数需要执行多长时间,以及执行时间和输入变量的相关性
	对比一个函数的多种实现方式

其实吧,你也不需要看我这个文章,因为官方文档写了很多很多的例子,上面有详细的英文注释,你都可以参考一下。毕竟官方文档的准确度必然是可信的。当然,你觉得英文费劲,喜欢看我这种写的大白话的,那也可以。
JMH官方例子
OK,至此你已经知道了这玩意到底是个啥,以及他到底能做啥了。下面我们就来看看他到底咋用。

四、使用JMH

1、集成JMH

这个东西的集成方式有很多种,都在官网上面有介绍,这里我们以IDEA的使用方式开始介绍,很简单。
生成 jar 文件的形式主要是针对一些比较大的测试,可能对机器性能或者真实环境模拟有一些需求,需要将测试方法写好了放在 linux 环境执行。可以打成jar包,在服务器上执行,也是支持的,比如模拟真实环境,就可以在服务器上运行jar,官网首页都有介绍。
具体操作就是:

$ mvn clean install
$ java -jar target/benchmarks.jar

这里我们模拟本地的IDE执行操作:
step1:创建一个maven项目,引入maven依赖。

<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>
    <scope>provided</scope>
</dependency>

然后我们就可以来使用他的一些操作了。这里说几个常见的问题。
1、你创建测试类的时候,一定要自己建个包,比如这样,不要直接写在java目录下面,有权限错误。
在这里插入图片描述
2、你启动测试类的时候最好是不要用debug模式启动,会有异常,本身就是性能测试,你debug是准备打断点还是咋。

2、使用JMH

我们使用的方式也很直白,就是直接怼官方那几个例子,我再用大白话翻译一下例子的内容,总结出来使用的一些方法和含义,毕竟官方的才是坠吼的。

2.1、sample1

示例1的官方地址

public class JMHSample_01_HelloWorld {

    @Benchmark
    public void wellHelloThere() {
        // this method was intentionally left blank.
    }
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JMHSample_01_HelloWorld.class.getSimpleName())
                .forks(1)
                .build();
        new Runner(opt).run();
    }
}

你看这个方法,很简单,它这个是个入门案例,毕竟第一个不要太复杂,先看个大纲,我们先来看一下这个案例,它分为两部分。
第一部分是一个方法名为wellHelloThere,方法什么都没有实现,是一个空方法。只是上面标注了一个注解**@Benchmark**。这个注解的意思就是表示对加这个注解的方法进行测试。
第二部分是一个main函数,里面看上去像是创建了一个对象,然后run了一下,我们先来看一下他的输出再来具体看。记住最好别用debug模式运行,有时候会报错。
最后会在控制台输出这么一段内容。我加点注释,后面用#写的汉字就是我的注释。

# JMH的版本,这个就是你maven文件引入的那个版本
# JMH version: 1.35 

# JDK的版本
# VM version: JDK 11.0.22, Java HotSpot(TM) 64-Bit Server VM, 11.0.22+9-LTS-219

# JDK的安装位置
# VM invoker: D:\env-soft\java\jdk11\jdk\bin\java.exe

# JVM的参数,现在我啥参数没加,都是默认的,后面可以JVM调优看一下影响
# VM options: -javaagent:D:\Program Files (x86)\IntelliJ IDEA 2021.2\lib\idea_rt.jar=57542:D:\Program Files (x86)\IntelliJ IDEA 2021.2\bin -Dfile.encoding=UTF-8

# 这个目前还没用到,回头说, Blackhole mode是黑洞模式
# Blackhole mode: full + dont-inline hint (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)

# 以下的都是比较重要的参数

# Warmup 表示预热,就是在正式进行测试你的加上@Benchmark的方法之前,做一次预热,运行起来,避免JIT的影响,这里5 iterations,10 s each表示预热要进行五轮,也就是要跑五个轮次,每个轮次跑十秒。注意这里,不是跑五次这个方法,而是五轮,每轮十秒,你这个空方法,每轮十秒下来能跑的次数那可大了去了。
# Warmup: 5 iterations, 10 s each

# 正式执行的次数,也是跑五轮,每轮跑十秒进行测试
# Measurement: 5 iterations, 10 s each

# 超时时间,每一轮各自的超时时间就是十分钟,过了十分钟还没跑完也结束了
# Timeout: 10 min per iteration

# 线程数,默认是1个线程跑,我们可以调整到时候,一个线程跑五轮不管是预热还是真实测试是,那都是串行执行的了,所以你看到下面的输出指标都是串行的。有时候你验证并发执行,可以调整一下。
# Threads: 1 thread, will synchronize iterations

# 输出模式为吞吐量模式,ops/time表示每秒执行多少次,吞吐量的模式
# Benchmark mode: Throughput, ops/time

# 执行的打@Benchmark注解的方法,com.levi.JMHSample_01_HelloWorld我们这个类只有wellHelloThere这一个方法加了@Benchmark注解,所以就执行他,这里其实可以看出来,你要是多个@Benchmark的方法,他会依次都执行,后面我们会看到多个的。
# Benchmark: com.levi.JMHSample_01_HelloWorld.wellHelloThere

# 这里我们先跳过,后面会介绍这里,先看大纲
# Run progress: 0.00% complete, ETA 00:01:40
# Fork: 1 of 1
# Warmup Iteration   1: 2506700198.155 ops/s
# Warmup Iteration   2: 2611047451.631 ops/s
# Warmup Iteration   3: 2555255646.980 ops/s
# Warmup Iteration   4: 2678825759.068 ops/s
# Warmup Iteration   5: 2803474171.878 ops/s
Iteration   1: 2791113907.236 ops/s
Iteration   2: 2779228951.815 ops/s
Iteration   3: 2724339734.541 ops/s
Iteration   4: 2758381926.323 ops/s
Iteration   5: 2758514249.394 ops/s

测试的结果报告如下:
Result “com.levi.JMHSample_01_HelloWorld.wellHelloThere”:

2762315753.862 ±(99.9%) 误差上下浮动百分之九十九的时候,每秒2762315753次
97936160.313 ops/s [Average] 平均吞吐
(min, avg, max) = (2724339734.541, 2762315753.862, 2791113907.236) 最小,平均,最大执行次数
stdev = 25433709.824 这是标准差
CI (99.9%): [2664379593.548, 2860251914.175]:百分之九十九的吞吐区间,可以看到大部分的时候执行结果

这个最后的统计结果是我们要关注的,我用分开每一行展示,不然会错行。
在这里插入图片描述
下面我分开行展示:
Benchmark :JMHSample_01_HelloWorld.wellHelloThere 表示你测试的方法,我们就一个加@Benchmark的方法,所以就一个。

Mode:thrpt 表示是以吞吐量模式测试的,也就是每秒执行几次 。

Cnt:5 表示执行的轮次,我们用的是默认的,就是5次。

Score:2762315753.862 ± 97936160.313 表示你本次测试的评分,越高越高,你可以把多种实现测完对分数做一下对比,就能看出性能了,其实这里我理解还是你的吞吐量,越高肯定性能越好。

Error:这是报错次数,有时候并发的时候,或者网络波动可能会有错,我们这里空方法,肯定没错。

Units:ops/s 输出结果的单位,其实我理解就是你分数的单位,分数本身就是一个吞吐量。

五、缩短时间

我们在测试的时候,发现一个问题,就是很慢才能运行出来结果,而且看控制台时间基本消耗在预热的五轮执行和正式执行的时候的五轮执行。所以我们这里可以使用一些注解来修改一下默认的这五轮开销,让我们看起来更迅速的得到结果,注意我们这里只是为了测试更快些,实际开发你要是做测试,需要自己判断要改大还是改小,比如你想充分预热,那就可以调大。这里只是说一下用法。

// 预热注解,修改为只预热一轮,每轮只跑一秒,默认是5,5这里改为1,1
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS)
// 测试执行注解,修改为只执行一轮,每轮只跑一秒,默认是5,5这里改为1,1
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS)

所以最后就变为这样。

// 预热注解,修改为只预热一轮,每轮只跑一秒,默认是5,5这里改为1,1
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS)
// 测试执行注解,修改为只执行一轮,每轮只跑一秒,默认是5,5这里改为1,1
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS)
public class JMHSample_01_02_HelloWorld {

    @Benchmark
    public void wellHelloThere() {
        // this method was intentionally left blank.
    }
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JMHSample_01_02_HelloWorld.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

这样修改只是为了观察方便,预热轮数变小必然预热不充分,执行轮数变小必然统计不是那么精确。实际生产记得不断调整观察。

六、总结

我们每一篇都会有一个总结,就是关于这些注解的使用和结果的观察。
不然内容分散在文中,有时候不太适合观察。

1、注解

@Warmup:用于方法和类上,预热注解,加在类上对所有@Benchmark生效,加在方法上对指定方法生效。
默认为预热五轮,每轮执行10秒。可以通过参数修改。
iterations:预热执行轮数
time:预热每轮执行时间
timeUnit:执行时间的单位,默认为秒

@Measurement:测试执行注解,用于方法和类上,测试执行注解,加在类上对所有@Benchmark生效,加在方法上对指定方法生效。
iterations:预热执行轮数
time:预热每轮执行时间
timeUnit:执行时间的单位,默认为秒

@Benchmark:用在方法上,被这个注解标识的方法表示要被测试跑的方法。

七、相关链接

八、参考链接

1、OpenJdk官方文档
2、https://www.cnblogs.com/kiwifly/p/11477153.html
3、https://blog.csdn.net/u012060033/article/details/130244468
4、https://www.bilibili.com/video/BV1aT41177QZ?p=6&vd_source=ed2188454468b8a76f32cc2e7188243c

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/526895.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM32的位操作(相当于51单片机的sbit)

经过一段时间的学习&#xff0c;今天发现STM32的单个端口都有一个32位的地址&#xff0c;这样就可以把这个地址给找出来&#xff0c;进行单个位的操作了&#xff0c;这也没有什么好说的&#xff0c;直接复制粘贴就好了&#xff0c;用到的时候过来复制直接使用就行了。虽然看着挺…

深入理解指针2:数组名理解、一维数组传参本质、二级指针、指针数组和数组指针、函数中指针变量

目录 1、数组名理解 2、一维数组传参本质 3、二级指针 4、指针数组和数组指针 5、函数指针变量 1、数组名理解 首先来看一段代码&#xff1a; int main() {int arr[10] { 1,2,3,4,5,6,7,8,9,10 };printf("%d\n", sizeof(arr));return 0; } 输出的结果是&…

Astra深度相机在Ubuntu18.04系统下实现相机标定

问题&#xff1a; 当使用Astra相机的启动的指令启动相机后&#xff0c;使用rviz查看相机所发布的rgb数据时&#xff0c;在终端会出现如下的提示信息&#xff1a; Camera calibration file /home/car/.ros/camera_info/rgb_Astra_Orbbec.yaml not found. Camera calibration fil…

深度学习基础之一:机器学习

文章目录 深度学习基本概念(Basic concepts of deep learning)机器学习典型任务机器学习分类 模型训练的基本概念基本名词机器学习任务流程模型训练详细流程正、反向传播学习率Batch size激活函数激活函数 sigmoid 损失函数MSE & M交叉熵损失 优化器优化器 — 梯度下降优化…

10.枚举

1.背景及定义 枚举是在JDK1.5以后引入的。 主要用途是&#xff1a; 将一组常量组织起来&#xff0c; 在这之前表示一组常量通常使用定义常量的方式&#xff1a; public static final int RED 1; public static final int GREEN 2; public static final int BLACK 3; 但是…

Java中线程安全集合类

Java中线程安全类可以分为三大类 遗留的线程安全集合如Hashtable、vectorJava.util.concurrent.*&#xff08;包含三类关键词&#xff1a;Blocking、CopyOnWrite、Concurrent&#xff09;使用Collections装饰的线程安全集合&#xff0c;如&#xff1a; Collections.synchroniz…

lottery-攻防世界

题目 flag在这里要用钱买&#xff0c;这是个赌博网站。注册个账号&#xff0c;然后输入七位数字&#xff0c;中奖会得到相应奖励。 githacker获取网站源码 &#xff0c;但是找到了flag文件但是没用。 bp 抓包发现api.php&#xff0c;并且出现我们的输入数字。 根据题目给的附…

搭建Flutter开发环境、从零基础到精通(文末送书【北大出版社】)

目录 搭建开发环境 1. 下载Flutter SDK 2. 设置镜像地址及环境变量 3. 安装与设置Android Studio 4. 安装Visual Studio Code与Flutter开发插件 5. IDE的使用和配置 6. 安装Xcode 7. 检查Flutter开发环境 好书推荐 内容简介 作者简介 搭建开发环境 Flutter可以跨平…

机器人客户端如何配置同步消息至多个群中

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂。 前言 由于微信群的人数&#xff0c;最多是500人&#xff0c;如果有人的业务做的大&#xff0c;可能会同步创建好多个群&#xff0c;但是资料的不想多个群一起发&#xff0c;发给某个群&a…

服务注册自治,降低 ASP.NET Core Web API 依赖注入的耦合度和复杂度

前言 在软件的实际开发中&#xff0c;一个软件通常由多个项目组成&#xff0c;这些项目都会直接或者间接被主 ASP.NET Core 项目引用。 这些项目中通常都会用到若干个被注入的服务&#xff0c;因此我们需要在主 ASP.NET Core 项目的 Program.cs 中注册这些服务。这样不仅会增…

【深入理解Java IO流0x03】解读Java最基本的IO流之字节流InputStream、OutputStream

在开始前&#xff0c;我们再来回顾一下这张图&#xff1a; 本篇博客主要为大家讲解字节流。 我们都知道&#xff0c;一切文件&#xff08;文本、视频、图片&#xff09;的数据都是以二进制的形式存储的&#xff0c;传输时也是。所以&#xff0c;字节流可以传输任意类型的文件数…

【数据结构】复杂度(长期维护)

本篇博客主要是浅谈数据结构概念及时间复杂度&#xff0c;并做长期的维护更新&#xff0c;有需要借鉴即可。 复杂度目录 一、初识数据结构1.基础概念2.如何学好数据结构 二、复杂度1.复杂度2.时间复杂度①有限数的时间复杂度②函数的时间复杂度③二分查找时间复杂度④递归拓展练…

转让名称带中国的金融控股集团公司要多少钱

随着公司的发展和市场竞争的影响&#xff0c;越来越多的创业者希望注册一家好名称的公司&#xff0c;以提高企业知名度和竞争力。但是&#xff0c;注册中字头无地域公司需要满足一定的条件和流程。本文将对中字头无地域公司注册条件及流程进行详细的介绍。可以致电咨询我或者来…

动态支付策略:Go 语言中策略模式的妙用

关注公众号【爱发白日梦的后端】分享技术干货、读书笔记、开源项目、实战经验、高效开发工具等&#xff0c;您的关注将是我的更新动力&#xff01; 在现代软件架构中&#xff0c;支付功能是不可或缺的一环。无论是在线购物还是虚拟服务&#xff0c;支付策略的选择直接影响用户体…

transform 模型常见问题

目录 transform 模型常见问题 transform 模型常见问题 1.Transformer为何使用多头注意力机制?(为什么不使用一个头) 答:多头可以使参数矩阵形成多个子空间,矩阵整体的size不变,只是改变了每个head对应的维度大小,这样做使矩阵对多方面信息进行学习,但是计算量和单个h…

JWT/JWS/JWE

JWT(JSON Web Token)&#xff1a;一种基于JSON格式&#xff0c;用于在Web应用中安全传递用户身份验证和授权信息的标准令牌&#xff0c;可以包含签名(JWS)和加密(JWE)的信息 MacAlgorithm(Message Authentication Code Algorithm)&#xff1a;消息认证码算法 HMAC(Hash-based…

【C++】详解 Unique 函数 (小白一看就懂!!!)

目录 一、前言 二、去重函数 Unique() ✨头文件 ✨用法与作用 ✨注意点 三、常考面试题 四、共勉 一、前言 经常刷算法题的朋友&#xff0c;肯定会经常看到题目中提到 去重 这样的字眼&#xff0c;或者需要我们通过 去重 来解题&#xff0c;由于之前对 去重 了解的不太清楚…

2024/4/1—力扣—删除字符使频率相同

代码实现&#xff1a; 思路&#xff1a; 步骤一&#xff1a;统计各字母出现频率 步骤二&#xff1a;频率从高到低排序&#xff0c;形成频率数组 步骤三&#xff1a;频率数组只有如下组合符合要求&#xff1a; 1, 0...0n 1, n...n (, 0)n...n, 1(, 0) bool equalFrequency(char…

【C++学习】哈希的应用—位图与布隆过滤器

目录 1.位图1.1位图的概念1.2位图的实现3.位图的应用 2.布隆过滤器2.1 布隆过滤器提出2.2布隆过滤器概念2.3如何选择哈希函数个数和布隆过滤器长度2.4布隆过滤器的实现2.4.1布隆过滤器插入操作2.4.2布隆过滤器查找操作2.4.3 布隆过滤器删除 2.5 布隆过滤器优点2.6布隆过滤器缺陷…

UE4_动画基础_角色的缩放

以第三人称模板进行制作。 一、首先为角色缩放新建粒子效果 1、新建niagara system&#xff0c;重命名为NS_Shrink。 2、双击打开设置参数&#xff1a; 发射器重命名&#xff1a; Emitter State&#xff1a; 发射器一次喷发数量&#xff1a; 粒子初始大小&#xff0c;生命周…