首页 > 编程语言 >JMH – Java基准测试

JMH – Java基准测试

时间:2023-07-03 17:23:45浏览次数:52  
标签:ElementType Java String 基准 METHOD JMH Calendar now public

官方资源

官方Github样例

应用场景

  • 对要使用的数据结构不确定,不知道谁的性能更好
  • 对历史方法代码重构,要评判改造之后的性能提升多少 (我要做的场景
  • 想准确地知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性
  • 对比接口不同实现在给定条件下的吞吐量
  • 查看多少百分比的请求在多长时间内完成

背景限制(防杠指南)

  • 业务场景?
    因为当前项目是接手比较老的项目,已经有成熟业务在跑,原先的生成模型是nextByCalendarAndRandom, 序号生成是采用两位随机数,然后随机数产生了冲突,一毫秒内产生的两个随机数有冲突,
  • 为什么不直接使用 snowflake?
    原先的生成逻辑 6(商户号) + 15(yyMMddHHmmssSSS 最大长度,可能比15小) + 2(随机数) = 23 (最大长度)
    如果使用雪花算法,则 6 + 19 = 25 (最大长度),且现在业务方较多,不确定对方是否有限制该字段长度,再就是如果对雪花算法进行裁剪,也不能保证肯定不会出现冲突,经衡量过后,暂时不使用雪花算法,后续业务方能确定长度没有问题,就可以升级
  • 这个算法不是分布式的,如果是两台服务器,则出现冲突的可能性就变大了
    是的,如果两台服务同时运行,然后又同时有请求进来,就有很大的可能性出现冲突,但现在的业务状况是单体架构,只不过做了主备服务,主服务宕机,备份才会启动,暂时不会两台服务同时启动
  • 那如果采用 nextByCalendarAndAtomicInteger 自增,就表示一毫秒最大只有100个请求能进来?超过就肯定会冲突?
    是的,这个也是业务决定的,如果我们当前的业务量超过每毫秒超100,那问题可能不是我这里的冲突了,服务会率先被压垮
  • 最终的业务采用什么方法?
    使用了 nextByLocalDateTimeAndAtomicInteger 方法,也有每毫秒超100必定重复的限制

引用依赖

        <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>

测试代码

@UtilityClass
public class IdWork {

    @Deprecated
    public static String nextByCalendarAndRandom(String merchantNo) {

        Calendar now = Calendar.getInstance();
        long random1 = Math.round(Math.random() * 9);
        long random2 = Math.round(Math.random() * 9);
        String timestamp = (now.get(Calendar.YEAR) + "").substring(2)
                + (now.get(Calendar.MONTH) + 1)
                + now.get(Calendar.DAY_OF_MONTH)
                + now.get(Calendar.HOUR_OF_DAY)
                + now.get(Calendar.MINUTE)
                + now.get(Calendar.SECOND)
                + now.get(Calendar.MILLISECOND);
        return merchantNo + timestamp + random1 + random2;
    }

    @Deprecated
    public static String nextByLocalDateTimeAndRandom(String merchantNo) {

        LocalDateTime now = LocalDateTime.now();
        long random1 = Math.round(Math.random() * 9);
        long random2 = Math.round(Math.random() * 9);
        String timestamp = (now.getYear() + "").substring(2)
                + now.getMonthValue()
                + now.getDayOfMonth()
                + now.getHour()
                + now.getMinute()
                + now.getSecond()
                + (now.getNano() / 1000000);
        return merchantNo + timestamp + random1 + random2;
    }

    @Deprecated
    public static String nextByCalendarAndAtomicInteger(String merchantNo) {

        Calendar now = Calendar.getInstance();
        String timestamp = (now.get(Calendar.YEAR) + "").substring(2)
                + (now.get(Calendar.MONTH) + 1)
                + now.get(Calendar.DAY_OF_MONTH)
                + now.get(Calendar.HOUR_OF_DAY)
                + now.get(Calendar.MINUTE)
                + now.get(Calendar.SECOND)
                + now.get(Calendar.MILLISECOND);
        return merchantNo + timestamp + getSeqNo();
    }

    @Deprecated
    public static String nextByLocalDateTimeAndAtomicInteger(String merchantNo) {

        LocalDateTime now = LocalDateTime.now();
        String timestamp = (now.getYear() + "").substring(2)
                + now.getMonthValue()
                + now.getDayOfMonth()
                + now.getHour()
                + now.getMinute()
                + now.getSecond()
                + (now.getNano() / 1000000);
        return merchantNo + timestamp + getSeqNo();
    }

    public static String nextBySnowflake(String merchantNo) {
        return merchantNo + IdGenerator.next();
    }

    private static AtomicInteger seqNo = new AtomicInteger(1);

    private static String getSeqNo() {

        int curSeqNo = seqNo.getAndIncrement();
        if (curSeqNo > 99) { // 重置,也可以取模
            seqNo = new AtomicInteger(1);
        }
        if (curSeqNo < 10) {
            return "0" + curSeqNo;
        }
        return curSeqNo + "";

    }

    public static void main(String[] args) {
        String next1 = IdWork.nextByCalendarAndRandom("900087");
        System.out.println(next1);
        String next2 = IdWork.nextByLocalDateTimeAndRandom("900087");
        System.out.println(next2);
        String next3 = IdWork.nextByCalendarAndAtomicInteger("900087");
        System.out.println(next3);
        String next4 = IdWork.nextByLocalDateTimeAndAtomicInteger("900087");
        System.out.println(next4);
        String next5 = IdWork.nextBySnowflake("900087");
        System.out.println(next5);
    }
}
public class IdTest {


    @Benchmark
    public String getIdBySnowflake() {
        return IdWork.nextBySnowflake("900087");
    }

    @Benchmark
    public String nextByCalendarAndRandom() {
        return IdWork.nextByCalendarAndRandom("900087");
    }

    @Benchmark
    public String nextByLocalDateTimeAndRandom() {
        return IdWork.nextByLocalDateTimeAndRandom("900087");
    }

    @Benchmark
    public String nextByCalendarAndAtomicInteger() {
        return IdWork.nextByCalendarAndAtomicInteger("900087");
    }

    @Benchmark
    public String nextByLocalDateTimeAndAtomicInteger() {
        return IdWork.nextByLocalDateTimeAndAtomicInteger("900087");
    }


    public static void main(String[] args) throws RunnerException {

        // 吞吐量
//        Options opt = new OptionsBuilder()
//                .include(IdTest.class.getSimpleName())
//                .mode(Mode.Throughput)
//                .forks(1)
//                .build();

        // 平均耗时
        Options opt = new OptionsBuilder()
                .include(IdTest.class.getSimpleName())
                .mode(Mode.AverageTime)
                .timeUnit(TimeUnit.NANOSECONDS)
                .forks(1)
                .build();

        new Runner(opt).run();

    }

// 吞吐量
//    Benchmark                                    Mode  Cnt        Score        Error  Units
//    IdTest.getIdBySnowflake                     thrpt    5  4070403.840 ±  11302.832  ops/s
//    IdTest.nextByCalendarAndAtomicInteger       thrpt    5  4201822.821 ± 177869.095  ops/s
//    IdTest.nextByCalendarAndRandom              thrpt    5  4085723.001 ±  47505.309  ops/s
//    IdTest.nextByLocalDateTimeAndAtomicInteger  thrpt    5  5036852.390 ± 153313.836  ops/s
//    IdTest.nextByLocalDateTimeAndRandom         thrpt    5  5199148.189 ± 405132.888  ops/s

// 平均耗时
//    Benchmark                                   Mode  Cnt    Score   Error  Units
//    IdTest.getIdBySnowflake                     avgt    5  245.739 ± 0.302  ns/op
//    IdTest.nextByCalendarAndAtomicInteger       avgt    5  239.174 ± 4.244  ns/op
//    IdTest.nextByCalendarAndRandom              avgt    5  251.084 ± 5.798  ns/op
//    IdTest.nextByLocalDateTimeAndAtomicInteger  avgt    5  197.332 ± 0.779  ns/op
//    IdTest.nextByLocalDateTimeAndRandom         avgt    5  212.105 ± 1.888  ns/op

}

概念理解

常用注解

类型 作用域 描述 备注
Benchmark ElementType.METHOD 最重要的注解,标记需要执行的方法
BenchmarkMode ElementType.METHOD, ElementType.TYPE 统计的维度,有吞吐量,平均耗时,也可以组合使用
Fork ElementType.METHOD, ElementType.TYPE 复制多个进程来执行方法,每轮默认Iteration循环5次,如果fork 3,则会执行3*5 次,一般默认值1就可以
Measurement ElementType.METHOD, ElementType.TYPE 方法控制:循环次数,每次循环时间以及对应的时间单位
Warmup ElementType.METHOD,ElementType.TYPE 预热,避免系统冷启动导致的性能测试不准
OutputTimeUnit ElementType.METHOD, ElementType.TYPE 输出时间单位,默认是秒
Param ElementType.FIELD 可以指定遍历参数,针对特殊字段测试不同的性能
Setup ElementType.METHOD 启动类设置,类似 junit Before类型注解
TearDown ElementType.METHOD 销毁类设置,类似junit After类型注解,一般用于销毁池化的资源
Threads ElementType.METHOD,ElementType.TYPE
Timeout ElementType.METHOD,ElementType.TYPE
AuxCounters ElementType.TYPE 辅助计数器,可以统计 @State 修饰的对象中的 public 属性被执行的情况
Group ElementType.METHOD
GroupThreads ElementType.METHOD
CompilerControl ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE 内联扩展是一种特别的用于消除调用函数时所造成的固有时间消耗方法,这里用来控制方法或类是否内联
OperationsPerInvocation ElementType.METHOD, ElementType.TYPE

BenchmarkMode 执行模式(可以多个组合执行)

类型 描述
Throughput 每段时间执行的次数,一般是秒
AverageTime 平均时间,每次操作的平均耗时
SampleTime 在测试中,随机进行采样执行的时间
SingleShotTime 在每次执行中计算耗时
All 所有模式
// 常用的注解
@BenchmarkMode({Mode.Throughput,Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
public class BenchmarkTest {
    @Benchmark
    public long test() {}
}

// 使用 OptionsBuilder 建造者模式构建 Options, 然后在main方法执行,建议使用
Options opt = new OptionsBuilder()
        .include(IdTest.class.getSimpleName())
        .mode(Mode.AverageTime)
        .mode(Mode.Throughput)
        .timeUnit(TimeUnit.NANOSECONDS)
        .warmupIterations(3)
        .warmupTime(TimeValue.seconds(1))
        .measurementIterations(5)
        .measurementTime(TimeValue.seconds(1))
        .forks(1)
        .build();

一些提示

避免循环

JVM会对循环进行优化,这样会导致获取的测试结果不准确。

引用资源

jmh-java-microbenchmark-harness
jenkov: java-performance
jmh-benchmark-with-examples
Java基准测试工具 —— JMH使用指南

标签:ElementType,Java,String,基准,METHOD,JMH,Calendar,now,public
From: https://www.cnblogs.com/javar/p/jmh.html

相关文章

  • Java 将秒转化为xx分xx秒
    代码privatestaticStringgetMinutes(intseconds){if(seconds>0){intremainder=seconds%60;intminutes=(seconds-remainder)/60;Stringminute=String.valueOf(minutes).length()<2?"0"+......
  • mac m1 安装java性能监控工具VisualVM 2.1
    macm1安装java性能监控工具VisualVM2.1.6背景本地已经安装了java8,在终端输入jvisualvm提示没有安装benjie@benjiedeMBP~%jvisualvmTheoperationcouldn’tbecompleted.UnabletolocateaJavaRuntimethatsupportsjvisualvm.Pleasevisithttp://www.java.com......
  • spring报错-Caused by: java.lang.IllegalArgumentException: Unsupported class file
    这个错误原因是因为JDK版本过高,改一下版本就行了把里面的19改成8这样就行了......
  • Java 常用注解@Configuration,@Bean及@ConfigurationProperties(prefix = "spring.data
    @ConfigurationpublicclassEventDataSourceConfig{@Bean(name="eventdataSource")@ConfigurationProperties(prefix="datasource.event")publicDataSourceoldDataSource(){returnDataSourceBuilder.create().build();......
  • JAVA设计模式之工厂模式
    设计模式设计模式(DesignPattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。总体来说设计模式分为三大类:创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、......
  • 在Java中使用Apache POI导入Excel文件并保留内容的换行符
    importorg.apache.poi.ss.usermodel.*;importorg.apache.poi.xssf.usermodel.XSSFWorkbook;importjava.io.FileInputStream;importjava.io.IOException;publicclassReadExcelWithNewlines{publicstaticvoidmain(String[]args){StringfilePat......
  • 电脑迷宫鼠(Java语言实现)
    电脑迷宫鼠基础要求1.概述:用java面向对象程序设计语言,设计和实现一电脑鼠走迷宫的软件程序,即一个假想的小车能在图示的迷宫中根据设定的起始点和终点自主寻找路径。本综合实践分成两部分:第一部分为算法设计和实现部分,第二部分为界面展现部分。2.第一部......
  • java8发送邮件失败, 修改jdk镜像中的java.security文件
    背景原本部署环境中的java:8镜像在检查问题删除,用dockerpullopenjdk:8重新拉取并改名java:8,在重新部署后,发送邮件报错:javax.net.ssl.SSLHandshakeException:Noappropriateprotocol(protocolisdisabledorciphersuitesareinappropriate)!!!原因根本原因是j......
  • Java线程池基础介绍
    一、线程池的优点1、线程池能够复用已经创建了的线程来执行任务,从而降低了频繁创建和销毁线程所带来的资源消耗;2、任务创建完成时,不必等待线程的创建,能够立即执行,提高了任务响应的速度。 二、创建线程池的七大核心参数1、corePoorSize核心线程数线......
  • JavaScript 中 object 的几个方法:entries,values,keys 对比
    在JavaScript中,对象(Object)是一种无序的键值对集合。以下是entries,values和keys这三个对象方法的比较: entries()方法:返回一个包含对象的键值对的数组。每个键值对都是一个数组,包含两个元素,第一个元素是键名,第二个元素是对应的值。1constobj={a:1,b:2,c:3};2conso......