最装逼的基准测试工具套件 - JMH

JMH简介


官网:http://openjdk.java.net/projects/code-tools/

github:https://github.com/openjdk/jmh

简介:JMH is a Java harness for building, running, and analysing nano/micro/milli/macro benchmarks written in Java and other languages targetting the JVM,

由简介可知,JMH不止能对Java语言做基准测试,还能对运行在JVM上的其他语言做基准测试。而且可以分析到纳秒级别。

推荐用法


官方推荐创建一个独立的Maven工程来运行JMH基准测试,这样更能确保结果的准确性。当然也可以在已存在的工程中,或者在IDE上运行,但是越复杂,结果越不可靠(more complex and the results are less reliable)。

简单实用


推荐用法通过命令行创建,构建和运行JMH基准测试。

setup1 
生成一个新的JMH工程的maven命令如下:

$ mvn archetype:generate \
-DinteractiveMode=false \
-DarchetypeGroupId=org.openjdk.jmh \
-DarchetypeArtifactId=jmh-java-benchmark-archetype \
-DgroupId=org.sample \
-DartifactId=test \
-Dversion=1.0

setup2
建立基准。生成项目后,可以使用以下Maven命令来构建它:

$ cd test/
$ mvn clean package

setup3
运行基准测试。构建完成后,您将获得独立的可执行文件JAR(该文件将保存您的基准测试)以及所有必要的JMH基础结构代码:

$ java -jar target/benchmarks.jar

压测代码 
这里我测试一下cglib、orika、spring等bean copy工具的性能

在上面生成的JMH工程里面的pom.xml引入依赖:

     <!-- spring-beans -->
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-beans</artifactId>
           <version>5.2.10.RELEASE</version>
       </dependency>
       <!-- cglib -->
       <dependency>
           <groupId>cglib</groupId>
           <artifactId>cglib</artifactId>
           <version>3.3.0</version>
       </dependency>
       <!-- commons-beanutils -->
       <dependency>
           <groupId>commons-beanutils</groupId>
           <artifactId>commons-beanutils</artifactId>
           <version>1.9.4</version>
       </dependency>
       <!-- orika -->
       <dependency>
           <groupId>ma.glasnost.orika</groupId>
           <artifactId>orika-core</artifactId>
           <version>1.5.4</version>
       </dependency>

测试类:

package org.sample;
 
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import net.sf.cglib.beans.BeanCopier;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.GenerateMicroBenchmark;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.sample.other.User;
import org.sample.other.UserService;
import org.sample.other.UserVO;
 
import java.util.concurrent.TimeUnit;
/**
 * 结果如下,重点关注Mean和Units两个字段,组合起来是1227.928ns/op,即每次操作耗时1227.928纳秒:Mean error表示误差,或者波动
 * Benchmark                               Mode   Samples         Mean   Mean error    Units
 * o.s.MyBenchmark.testApacheBeanUtils     avgt         5   306706.144    46350.093    ns/op
 * o.s.MyBenchmark.testCglibBeanCopier     avgt         5      159.403        3.243    ns/op
 * o.s.MyBenchmark.testDeadCode            avgt         5       99.916        2.527    ns/op
 * o.s.MyBenchmark.testOrikaBeanCopy       avgt         5      825.971       15.891    ns/op
 * o.s.MyBenchmark.testSpringBeanUtils     avgt         5    12034.272      307.476    ns/op
 *
 * 如果我们将@BenchmarkMode(Mode.AverageTime)与@OutputTimeUnit(TimeUnit.NANOSECONDS)的组合,改成@BenchmarkMode(Mode.Throughput)和@OutputTimeUnit(TimeUnit.MILLISECONDS),那么基准测试结果就是每毫秒的吞吐量(即每毫秒多少次操作),结果如下,表示943.437ops/ms:
 *
 */
 
 
/**
 * 基准测试后对代码预热总计5秒(迭代5次,每次1秒)。预热对于压测来说非常非常重要,如果没有预热过程,压测结果会很不准确。
 * # Warmup Iteration   2: 13349.349 ns/op
 * # Warmup Iteration   3: 12084.938 ns/op
 * # Warmup Iteration   4: 12032.203 ns/op
 * # Warmup Iteration   5: 12127.331 ns/op
 */
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
/**
 * 循环运行5次,每次迭代时间为1秒,总计5秒时间。
 */
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
/**
 * 表示fork多少个线程运行基准测试,如果@Fork(1),那么就是一个线程,这时候就是同步模式。
 */
@Fork(1)
/**
 * 基准测试模式,@BenchmarkMode(Mode.AverageTime)搭配@OutputTimeUnit(TimeUnit.NANOSECONDS)
 * 表示每次操作需要的平均时间,而OutputTimeUnit申明为纳秒,所以基准测试单位是ns/op,即每次操作的纳秒单位平均时间
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {
 
    /**
     * 被拷贝的源对象
     */
    @State(Scope.Benchmark)
    public static class CommonState {
        User user;
 
        @Setup(Level.Trial)
        public void prepare() {
            user = new UserService().get();
        }
    }
 
    /**
     * 测试手动getter/setter拷贝对象属性
     * @param commonState
     * @return UserVO
     * @throws Exception
     */
    @GenerateMicroBenchmark
    public UserVO testDeadCode(MyBenchmark.CommonState commonState) throws Exception {
        UserVO userVO = new UserVO();
        User user = commonState.user;
        userVO.setAccount(user.getAccount());
        userVO.setAddress(user.getAddress());
        userVO.setAge(user.getAge());
        userVO.setBirthday(user.getBirthday());
        userVO.setDepartment(user.getDepartment());
        userVO.setDiploma(user.getDiploma());
        userVO.setEmail(user.getEmail());
        userVO.setFax(user.getFax());
        userVO.setId(user.getId());
        userVO.setIdCard(user.getIdCard());
        userVO.setInnerTel(user.getInnerTel());
        userVO.setJob(user.getJob());
        userVO.setJoinDate(user.getJoinDate());
        userVO.setLeaved(user.getLeaved());
        userVO.setLoginDate(user.getLoginDate());
        userVO.setMaritalStatus(user.getMaritalStatus());
        userVO.setMobile(user.getMobile());
        userVO.setName(user.getName());
        userVO.setNo(user.getNo());
        userVO.setOuterTel(user.getOuterTel());
        userVO.setPassword(user.getPassword());
        userVO.setPermanentAddress(user.getPermanentAddress());
        userVO.setPicture(user.getPicture());
        userVO.setPinyin(user.getPinyin());
        userVO.setPosition(user.getPosition());
        userVO.setQq(user.getQq());
        userVO.setSex(user.getSex());
        userVO.setStatus(user.getStatus());
        userVO.setType(user.getType());
        userVO.setWeixin(user.getWeixin());
        userVO.setWeixinQrcode(user.getWeixinQrcode());
        userVO.setField00(user.getField00());
        userVO.setField01(user.getField01());
        userVO.setField02(user.getField02());
        userVO.setField03(user.getField03());
        userVO.setField04(user.getField04());
        userVO.setField05(user.getField05());
        userVO.setField06(user.getField06());
        userVO.setField07(user.getField07());
        userVO.setField08(user.getField08());
        userVO.setField09(user.getField09());
        userVO.setField10(user.getField10());
        userVO.setField11(user.getField11());
        userVO.setField12(user.getField12());
        userVO.setField13(user.getField13());
        userVO.setField14(user.getField14());
        userVO.setField15(user.getField15());
        userVO.setField16(user.getField16());
        userVO.setField17(user.getField17());
        userVO.setField18(user.getField18());
        userVO.setField19(user.getField19());
        userVO.setField20(user.getField20());
        userVO.setField21(user.getField21());
        userVO.setField22(user.getField22());
        userVO.setField23(user.getField23());
        userVO.setField24(user.getField24());
        userVO.setField25(user.getField25());
        userVO.setField26(user.getField26());
        userVO.setField27(user.getField27());
        userVO.setField28(user.getField28());
        userVO.setField29(user.getField29());
        userVO.setField30(user.getField30());
        userVO.setField31(user.getField31());
        userVO.setField32(user.getField32());
        userVO.setField33(user.getField33());
        userVO.setField34(user.getField34());
        userVO.setField35(user.getField35());
        userVO.setField36(user.getField36());
        userVO.setField37(user.getField37());
        userVO.setField38(user.getField38());
        userVO.setField39(user.getField39());
        userVO.setField40(user.getField40());
        userVO.setField41(user.getField41());
        userVO.setField42(user.getField42());
        userVO.setField43(user.getField43());
        userVO.setField44(user.getField44());
        userVO.setField45(user.getField45());
        userVO.setField46(user.getField46());
        userVO.setField47(user.getField47());
        userVO.setField48(user.getField48());
        userVO.setField49(user.getField49());
        userVO.setField50(user.getField50());
        userVO.setField51(user.getField51());
        userVO.setField52(user.getField52());
        userVO.setField53(user.getField53());
        userVO.setField54(user.getField54());
        userVO.setField55(user.getField55());
        userVO.setField56(user.getField56());
        userVO.setField57(user.getField57());
        userVO.setField58(user.getField58());
        userVO.setField59(user.getField59());
        userVO.setField60(user.getField60());
        userVO.setField61(user.getField61());
        userVO.setField62(user.getField62());
        userVO.setField63(user.getField63());
        userVO.setField64(user.getField64());
        userVO.setField65(user.getField65());
        userVO.setField66(user.getField66());
        userVO.setField67(user.getField67());
        userVO.setField68(user.getField68());
        userVO.setField69(user.getField69());
        userVO.setField70(user.getField70());
        userVO.setField71(user.getField71());
        userVO.setField72(user.getField72());
        userVO.setField73(user.getField73());
        userVO.setField74(user.getField74());
        userVO.setField75(user.getField75());
        userVO.setField76(user.getField76());
        userVO.setField77(user.getField77());
        userVO.setField78(user.getField78());
        userVO.setField79(user.getField79());
        userVO.setField80(user.getField80());
        userVO.setField81(user.getField81());
        userVO.setField82(user.getField82());
        userVO.setField83(user.getField83());
        userVO.setField84(user.getField84());
        userVO.setField85(user.getField85());
        userVO.setField86(user.getField86());
        userVO.setField87(user.getField87());
        userVO.setField88(user.getField88());
        userVO.setField89(user.getField89());
        userVO.setField90(user.getField90());
        userVO.setField91(user.getField91());
        userVO.setField92(user.getField92());
        userVO.setField93(user.getField93());
        userVO.setField94(user.getField94());
        userVO.setField95(user.getField95());
        userVO.setField96(user.getField96());
        userVO.setField97(user.getField97());
        userVO.setField98(user.getField98());
        userVO.setField99(user.getField99());
 
        assert "zzs0".equals(userVO.getName());
        return userVO;
    }
 
 
    /**
     * 测试apache BeanUtils拷贝对象属性
     * @param commonState
     * @return UserVO
     * @throws Exception
     */
    @GenerateMicroBenchmark
    public UserVO testApacheBeanUtils(MyBenchmark.CommonState commonState) throws Exception {
        UserVO userVO = new UserVO();
        org.apache.commons.beanutils.BeanUtils.copyProperties(userVO, commonState.user);
        assert "zzs0".equals(userVO.getName());
        return userVO;
    }
 
    /**
     * 测试spring BeanUtils拷贝对象属性
     * @param commonState
     * @return UserVO
     * @throws Exception
     */
    @GenerateMicroBenchmark
    public UserVO testSpringBeanUtils(MyBenchmark.CommonState commonState) throws Exception {
        UserVO userVO = new UserVO();
        org.springframework.beans.BeanUtils.copyProperties(commonState.user, userVO);
        assert "zzs0".equals(userVO.getName());
        return userVO;
    }
 
    /**
     * 测试cglib BeanCopier拷贝对象属性
     * @param commonState
     * @return UserVO
     * @throws Exception
     */
    @GenerateMicroBenchmark
    public UserVO testCglibBeanCopier(MyBenchmark.CommonState commonState) throws Exception {
        BeanCopier copier = BeanCopier.create(commonState.user.getClass(), UserVO.class, false);
        UserVO userVO = new UserVO();
        copier.copy(commonState.user, userVO, null);
        assert "zzs0".equals(userVO.getName());
        return userVO;
    }
 
    /**
     * 测试使用orika拷贝对象属性
     * @param commonState
     * @return UserVO
     * @throws Exception
     */
    @GenerateMicroBenchmark
    public UserVO testOrikaBeanCopy(MyBenchmark.CommonState commonState, MyBenchmark.OrikaState orikaState) throws Exception {
        MapperFacade mapperFacade = orikaState.mapperFactory.getMapperFacade();
        UserVO userVO = mapperFacade.map(commonState.user, UserVO.class);
        assert "zzs0".equals(userVO.getName());
        return userVO;
    }
 
    @State(Scope.Benchmark)
    public static class OrikaState {
        MapperFactory mapperFactory;
        @Setup(Level.Trial)
        public void prepare() {
            mapperFactory = new DefaultMapperFactory.Builder().build();
        }
    }

这里省略了一些实体类,测试类里面有对于注解的注释。

写完测试类执行mvn clean package 打包
然后执行java -jar target/benchmarks.jar
得到结果:

  Benchmark                         Mode  Samples    Mean         Mean error   Units
 o.s.MyBenchmark.testApacheBeanUtils avgt    5      306706.144   46350.093     ns/op
 o.s.MyBenchmark.testCglibBeanCopier avgt    5      159.403      3.243         ns/op
 o.s.MyBenchmark.testDeadCode        avgt    5      99.916       2.527         ns/op
 o.s.MyBenchmark.testOrikaBeanCopy   avgt    5      825.971      15.891        ns/op
 o.s.MyBenchmark.testSpringBeanUtils avgt    5      12034.272    307.476       ns/op

根据测试结果,对象拷贝速度方面: 

手动拷贝 > cglib beanCopier > orika mapper > spring beanUtils > apache commons beanUtils

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要将 JMH 测试结果转换为折线图,可以使用以下步骤: 1. 将 JMH 测试结果保存为 CSV 文件。可以通过在运行测试时添加 `-rff filename.csv` 参数来实现。这将生成一个名为 filename.csv 的文件,其中包含测试结果。 2. 使用一个图表库,例如 JFreeChart 或 Chart.js,读取 CSV 文件并将数据绘制为折线图。 如果使用 JFreeChart,可以按照以下步骤进行操作: 1. 将 JFreeChart 添加到项目的依赖中。 2. 使用 CSV 文件中的数据创建一个 `TimeSeriesCollection` 对象。 3. 创建一个 `JFreeChart` 对象,并将 `TimeSeriesCollection` 对象添加到其中。 4. 将 `JFreeChart` 对象添加到一个 `ChartPanel` 对象中。 5. 将 `ChartPanel` 添加到一个 `JFrame` 中,并显示窗口。 以下是一个简单的示例代码: ```java import java.io.File; import java.io.IOException; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.data.time.Millisecond; import org.jfree.data.time.TimeSeries; import org.jfree.data.time.TimeSeriesCollection; import org.jfree.data.xy.XYDataset; import org.jfree.ui.ApplicationFrame; import org.jfree.ui.RefineryUtilities; import com.opencsv.CSVReader; public class JMHChart extends ApplicationFrame { private static final long serialVersionUID = 1L; private TimeSeries series; public JMHChart(final String title) { super(title); this.series = new TimeSeries("JMH Benchmark Results", Millisecond.class); final TimeSeriesCollection dataset = new TimeSeriesCollection(this.series); final JFreeChart chart = createChart(dataset); final ChartPanel chartPanel = new ChartPanel(chart); chartPanel.setPreferredSize(new java.awt.Dimension(800, 600)); setContentPane(chartPanel); } private JFreeChart createChart(final XYDataset dataset) { return ChartFactory.createTimeSeriesChart( "JMH Benchmark Results", "Time", "Score", dataset, true, true, false ); } public void addData(File csvFile) throws IOException { CSVReader reader = new CSVReader(new FileReader(csvFile)); String[] line; while ((line = reader.readNext()) != null) { try { long timestamp = Long.parseLong(line[0]); double score = Double.parseDouble(line[1]); series.add(new Millisecond(timestamp), score); } catch (NumberFormatException e) { // ignore invalid lines } } reader.close(); } public static void main(final String[] args) throws IOException { final JMHChart demo = new JMHChart("JMH Benchmark Results"); demo.pack(); RefineryUtilities.centerFrameOnScreen(demo); demo.setVisible(true); demo.addData(new File("filename.csv")); } } ``` 这个示例代码将 CSV 文件中的数据添加到 `TimeSeries` 对象中,并使用 `createChart` 方法创建一个 `JFreeChart` 对象。然后将 `JFreeChart` 对象添加到 `ChartPanel` 中,并显示窗口。你需要根据你的 CSV 文件的格式和需要绘制的图表类型进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值