JMH性能测试:JSON工具测试

JMH(Java Microbenchmark Harness): 是专门用于微基准测试的api工具,我们可以通过JMH来对热点函数进行定量的分析,然后进行优化。
比较典型的应用就是:
* 精准知道某方法的执行时长,以及执行时间t和输入p之间的相关性;
* 比较不同接口实现,查找最佳性能方法
官方地址

先用maven将依赖引入:

	<!--JMH性能测试的-->
	<dependency>
		<groupId>org.openjdk.jmh</groupId>
		<artifactId>jmh-core</artifactId>
		<version>${jmh.version}</version>
	</dependency>
	<dependency>
		<groupId>org.openjdk.jmh</groupId>
		<artifactId>jmh-generator-annprocess</artifactId>
		<version>${jmh.version}</version>
		<scope>provided</scope>
	</dependency>

我们先来一个简单的样例来了解下JMH:String相加和使用StringBuilder的append方式的性能比较测试。


// BenchmarkMode表示基准测试的模型,Throughput是表示吞吐量,可以得到单位时间内的执行的次数数,另外的一些值:
// AverageTime:每次调用的平均消耗时间。
// SampleTime:随机取样的方式。
// SingleShotTime:只运行一次,往往同时把 warmup 次数设为0,用于测试冷启动时的性能。
// All:所有的模式
@BenchmarkMode(Mode.Throughput)
// 这个设置是为了预热代码,这是考虑了JIT编译机制,使得测试更加符合实际情形,iterations是迭代的次数,也就是执行次数,time是一次迭代需要执行的时间
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
// 这个是正常的代码测试,参数意义和上面一样
@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS)
// 测试进程数
@Threads(5)
// 这个是fork的进程分支数
@Fork(2)
// 这个是基准测试的时间单位
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class JSONBenchmarkTest {

    @Benchmark
    public void testString(){
        String str = "";
        for (int i = 0; i < 10; i++) {
            str += "a";
        }
    }

    @Benchmark
    public void testStringBuilder(){
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 10; i++) {
            sb.append("a");
        }
    }

    public static void main(String[] args) throws RunnerException {
        Options optional = new OptionsBuilder().include(JSONBenchmarkTest.class.getSimpleName()).build();
        new Runner(optional).run();
    }
}

测试运行的方式有多种,你也可以生成jar包运行,也可以自己写个测试单元,我们来看看运行结果:

# JMH version: 1.21
# VM version: JDK 1.8.0_241, Java HotSpot(TM) 64-Bit Server VM, 25.241-b07
# VM invoker: C:\Program Files\Java\jdk1.8.0_241\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.3.4\lib\idea_rt.jar=56074:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.3.4\bin -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 5 s each
# Timeout: 10 min per iteration
# Threads: 5 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.ryoma.test.JSONBenchmarkTest.testString

# Run progress: 0.00% complete, ETA 00:02:00
# Fork: 1 of 2
# Warmup Iteration   1: 15483.501 ops/ms
# Warmup Iteration   2: 19191.194 ops/ms
# Warmup Iteration   3: 20264.030 ops/ms
# Warmup Iteration   4: 19480.834 ops/ms
# Warmup Iteration   5: 23256.853 ops/ms
Iteration   1: 15957.031 ops/ms
Iteration   2: 22213.562 ops/ms
Iteration   3: 18531.152 ops/ms
Iteration   4: 15090.877 ops/ms
Iteration   5: 14872.852 ops/ms

# Run progress: 25.00% complete, ETA 00:01:34
# Fork: 2 of 2
# Warmup Iteration   1: 9488.254 ops/ms
# Warmup Iteration   2: 14901.396 ops/ms
# Warmup Iteration   3: 15147.344 ops/ms
# Warmup Iteration   4: 14724.827 ops/ms
# Warmup Iteration   5: 16359.645 ops/ms
Iteration   1: 17793.775 ops/ms
Iteration   2: 15591.598 ops/ms
Iteration   3: 17155.395 ops/ms
Iteration   4: 20171.710 ops/ms
Iteration   5: 14919.348 ops/ms


Result "com.ryoma.test.JSONBenchmarkTest.testString":
  17229.730 ±(99.9%) 3746.976 ops/ms [Average]
  (min, avg, max) = (14872.852, 17229.730, 22213.562), stdev = 2478.393
  CI (99.9%): [13482.754, 20976.706] (assumes normal distribution)


# JMH version: 1.21
# VM version: JDK 1.8.0_241, Java HotSpot(TM) 64-Bit Server VM, 25.241-b07
# VM invoker: C:\Program Files\Java\jdk1.8.0_241\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.3.4\lib\idea_rt.jar=56074:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.3.4\bin -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 1 s each
# Measurement: 5 iterations, 5 s each
# Timeout: 10 min per iteration
# Threads: 5 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.ryoma.test.JSONBenchmarkTest.testStringBuilder

# Run progress: 50.00% complete, ETA 00:01:03
# Fork: 1 of 2
# Warmup Iteration   1: 29986.558 ops/ms
# Warmup Iteration   2: 31278.352 ops/ms
# Warmup Iteration   3: 34750.060 ops/ms
# Warmup Iteration   4: 32943.155 ops/ms
# Warmup Iteration   5: 32914.852 ops/ms
Iteration   1: 34710.068 ops/ms
Iteration   2: 33898.339 ops/ms
Iteration   3: 35172.227 ops/ms
Iteration   4: 34691.384 ops/ms
Iteration   5: 34007.925 ops/ms

# Run progress: 75.00% complete, ETA 00:00:31
# Fork: 2 of 2
# Warmup Iteration   1: 29339.982 ops/ms
# Warmup Iteration   2: 29560.675 ops/ms
# Warmup Iteration   3: 65644.791 ops/ms
# Warmup Iteration   4: 76074.187 ops/ms
# Warmup Iteration   5: 75281.694 ops/ms
Iteration   1: 71302.248 ops/ms
Iteration   2: 76488.885 ops/ms
Iteration   3: 76566.340 ops/ms
Iteration   4: 76071.468 ops/ms
Iteration   5: 74394.781 ops/ms


Result "com.ryoma.test.JSONBenchmarkTest.testStringBuilder":
  54730.367 ±(99.9%) 32328.809 ops/ms [Average]
  (min, avg, max) = (33898.339, 54730.367, 76566.340), stdev = 21383.506
  CI (99.9%): [22401.557, 87059.176] (assumes normal distribution)


# Run complete. Total time: 00:02:07

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Benchmark                             Mode  Cnt      Score       Error   Units
JSONBenchmarkTest.testString         thrpt   10  17229.730 ±  3746.976  ops/ms
JSONBenchmarkTest.testStringBuilder  thrpt   10  54730.367 ± 32328.809  ops/ms

Process finished with exit code 0

当然你也可以把运行结果通过日志打印出来.output(“D://text.log”).build();
通过结果我们可以看出来:会输出每条线程的详细信息,最后还有一个总结的输出信息,从最后的信息可以看出来,StringBuilder比String的效率还是
高很多的,这里输出的吞吐量,值越高越好。还有些方法属性级别的注解:

  • @Benchmark:方法级别的注解,表示需要benchmark的对象,类似于单元测试的@Test。
  • @Setup:方法级的注解,可以用于初始化一些数据操作。
  • @TearDown:方法级的注解,可用于测试后的结束工作。
  • @State:当使用@Setup和@TearDown注解时都需要使用这个注解,该注解是类注解,表示类的共享情况
    • @State(Scope.Benchmark):表示所有线程间共享
    • @State(Scope.Group):表示同一个组里面所有线程共享
    • @State(Scope.Thread):表示每个线程独享
  • @Param:属性级的注解,可以用于测试不同参数的方法性能。

其他详细的注解推荐大家到这个博文查看:JMH性能测试

进入正题测试,我们最常见的是4种JSON使用工具,如下:

  • Gson:Google开源的解析json格式数据的工具,功能很全很强大,主要有toJson和fromJson两个函数,无其他的类库依赖,
    托管地址:gson
  • FastJson:阿里开源的解析json格式的数据工具,速度够快,无其他类库依赖,
    托管地址:fastjson
  • Jackson:托管在github上的开源项目,Jackson社区相对比较活跃,更新速度也比较快,springMvc的默认json解析神器,解析大的json
    文件比较快,运行内存低,优点多多啊。
    托管地址:jackson
  • Json-lib:是java的一个类库,项目地址:json-lib

首先我们来添加maven的依赖:

	<!--json工具-->
	<!--阿里的开源json工具-->
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>fastjson</artifactId>
		<version>1.2.68</version>
	</dependency>
	<!--Google开源的json工具-->
	<dependency>
		<groupId>com.google.code.gson</groupId>
		<artifactId>gson</artifactId>
		<version>2.8.6</version>
	</dependency>
	<!--java类库提供的-->
	<dependency>
		<groupId>net.sf.json-lib</groupId>
		<artifactId>json-lib</artifactId>
		<version>2.4</version>
	</dependency>
	<!--开源的第三方库json工具-->
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-databind</artifactId>
		<version>2.9.4</version>
	</dependency>
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-annotations</artifactId>
		<version>2.9.4</version>
	</dependency>

先准备json的相关方法代码:


	private static Gson gson = new GsonBuilder().create();
    private static ObjectMapper objectMapper = new ObjectMapper();
	
	/** gson的bean转为json */
    public String gsonBean2Json(Object object){
        return gson.toJson(object);
    }
    /** gson的json转为bean */
    public <T> T gsonJson2Bean(String json, Class<T> cls){
        return gson.fromJson(json, cls);
    }
    /** fastjson的bean转为json */
    public String fastjsonBean2Json(Object object){
        return JSON.toJSONString(object);
    }
    /** fastjson的json转为bean */
    public <T> T fastjsonJson2Bean(String json, Class<T> cls){
        return JSON.parseObject(json, cls);
    }
    /** jackson的bean转为json */
    public String jacksonBean2Json(Object object) throws JsonProcessingException {
        return objectMapper.writeValueAsString(object);
    }
    /** jackson的json转为bean */
    public <T> T jacksonJson2Bean(String json, Class<T> cls) throws JsonProcessingException {
        return objectMapper.readValue(json, cls);
    }
    /** jsonlib的bean转为json */
    public String jsonlibBean2Json(Object object){
        return JSONObject.fromObject(object).toString();
    }
    /** jsonlib的json转为bean */
    public <T> T jsonlibJson2Bean(String json, Class<T> cls){
        return (T) JSONObject.toBean(JSONObject.fromObject(json), cls);
    }

实体类代码:

@Data
public class PersonEntity {
    private int age;
    private String name;
    private List<PersonEntity> friendList;
    private Map<String, Object> hobbiesMap;
}

初始化数据:

	@Setup
    public void initData(){
        PersonEntity person = new PersonEntity();
        person.setAge(20);
        person.setName("序列化测试");
        List<PersonEntity> personEntities = Lists.newArrayList();
        PersonEntity p1 = new PersonEntity();
        p1.setAge(20);
        p1.setName("朋友1");
        PersonEntity p2 = new PersonEntity();
        p2.setAge(20);
        p2.setName("朋友2");
        personEntities.add(p1);
        personEntities.add(p2);
        person.setFriendList(personEntities);
        Map<String, Object> map = Maps.newHashMap();
        map.put("打篮球", "最爱");
        person.setHobbiesMap(map);
        personEntity = person;
    }

测试类注解:

@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
// 表示所有线程间共享
@State(Scope.Benchmark)
public class JSONBenchmarkTest {
	.....
}

序列化对象测试:


	@Param({"1000", "10000", "100000"})
    private int count;
    private PersonEntity personEntity;
	
	@Benchmark
    public void gsonb2j(){
        for (int i = 0; i < count; i++) {
            gsonBean2Json(personEntity);
        }
    }
    @Benchmark
    public void fastJsonb2j(){
        for (int i = 0; i < count; i++) {
            fastjsonBean2Json(personEntity);
        }
    }
    @Benchmark
    public void jacksonb2j() throws JsonProcessingException {
        for (int i = 0; i < count; i++) {
            jacksonBean2Json(personEntity);
        }
    }
    @Benchmark
    public void jsonlibb2j(){
        for (int i = 0; i < count; i++) {
            jsonlibBean2Json(personEntity);
        }
    }
	
	public static void main(String[] args) throws RunnerException {
        Options optional = new OptionsBuilder().include(JSONBenchmarkTest.class.getSimpleName()).forks(1).warmupIterations(0).build();
        new Runner(optional).run();
    }

运行后的测试结果,我们直接来看看最后的总结数据:

Benchmark                      (count)  Mode  Cnt     Score   Error  Units
JSONBenchmarkTest.fastJsonb2j     1000    ss        156.439          ms/op
JSONBenchmarkTest.fastJsonb2j    10000    ss        182.629          ms/op
JSONBenchmarkTest.fastJsonb2j   100000    ss        222.440          ms/op
JSONBenchmarkTest.gsonb2j         1000    ss         46.276          ms/op
JSONBenchmarkTest.gsonb2j        10000    ss        112.191          ms/op
JSONBenchmarkTest.gsonb2j       100000    ss        452.821          ms/op
JSONBenchmarkTest.jacksonb2j      1000    ss         82.042          ms/op
JSONBenchmarkTest.jacksonb2j     10000    ss        151.490          ms/op
JSONBenchmarkTest.jacksonb2j    100000    ss        308.667          ms/op
JSONBenchmarkTest.jsonlibb2j      1000    ss        343.709          ms/op
JSONBenchmarkTest.jsonlibb2j     10000    ss        917.391          ms/op
JSONBenchmarkTest.jsonlibb2j    100000    ss       3304.863          ms/op

数据越多的话fastjson的效率就越高,gson和jackson都是稳步上升,jsonlib则比较差。

我们来看看反序列的测试结果,以下有些改动:
初始化数据的时候,我们会把之前的person实例对象变成json格式的字符串:

	...跟上面的一样...
	person.setHobbiesMap(map);
    personJson = JSON.toJSONString(person);

	private String personJson;
	@Benchmark
    public void gsonj2b(){
        for (int i = 0; i < count; i++) {
            gsonJson2Bean(personJson, PersonEntity.class);
        }
    }
    @Benchmark
    public void fastJsonj2b(){
        for (int i = 0; i < count; i++) {
            fastjsonJson2Bean(personJson, PersonEntity.class);
        }
    }
    @Benchmark
    public void jacksonj2b() throws IOException {
        for (int i = 0; i < count; i++) {
            jacksonJson2Bean(personJson, PersonEntity.class);
        }
    }
    @Benchmark
    public void jsonlibj2b(){
        for (int i = 0; i < count; i++) {
            jsonlibJson2Bean(personJson, PersonEntity.class);
        }
    }

看看反序列化的测试结果:


Benchmark                      (count)  Mode  Cnt     Score   Error  Units
JSONBenchmarkTest.fastJsonj2b     1000    ss         58.142          ms/op
JSONBenchmarkTest.fastJsonj2b    10000    ss         96.720          ms/op
JSONBenchmarkTest.fastJsonj2b   100000    ss        268.407          ms/op
JSONBenchmarkTest.gsonj2b         1000    ss         42.239          ms/op
JSONBenchmarkTest.gsonj2b        10000    ss         90.028          ms/op
JSONBenchmarkTest.gsonj2b       100000    ss        307.894          ms/op
JSONBenchmarkTest.jacksonj2b      1000    ss        107.127          ms/op
JSONBenchmarkTest.jacksonj2b     10000    ss        175.029          ms/op
JSONBenchmarkTest.jacksonj2b    100000    ss        333.688          ms/op
JSONBenchmarkTest.jsonlibj2b      1000    ss        498.235          ms/op
JSONBenchmarkTest.jsonlibj2b     10000    ss       1313.649          ms/op
JSONBenchmarkTest.jsonlibj2b    100000    ss       5141.867          ms/op

反序列的测试结果可以看出和序列化的结论是差不多的,最后补充下,虽然fastjson的速度很快,但是最近频频出bug,
还是得慎重考虑选择哪个。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值