无惧性能烦恼-12款Bean拷贝工具压测大比拼
# 背景
在开发过程中,我们通常会用到DO、DTO、VO、PO等对象,一般来说这些对象之间的字段具有一定的相似性。在进行对象转换时,除了手动get/set之外,开发者大概率会使用到类似BeanUtils等对象拷贝工具类。由于许多拷贝工具类性能低下,开发者经常在工具类没有进行选型的情况下引入项目,造成了开发社区或公司对这类工具类使用时有了更多的性能担忧。在前期的调研当中,也有类似于本文的比较,大多数使用循环/StopWatch/计算执行时间等形式衡量,少数文章采用了压测的方法。这类评价方式,能反应出一定的性能问题,但通常实验做的不够严谨准确。
本文将对比市面上10款常见拷贝工具+1款基本封装的个人工具+1原生get/set方法,采用JMH进行公平性压测比较。以此让我们对工具类有一个清晰的对比,选择出合适的工具类。
实验代码 https://github.com/benym/benchmark-test (opens new window)
# 对比方法
get/set
: 原生get/setRpamisBeanUtils
: 基于Cglib BeanCopier+ConcurrentReferenceHashMap封装、基于ASM字节码拷贝原理MapStruct
: 编译器生成get/setBeanCopier
: 原生Cglib BeanCopier、基于ASM字节码拷贝原理JackSon
: Spring官方JackSon序列化工具ObjectMapperFastJson
: Alibaba Json序列化工具Hutool BeanUtil
: Hutool提供的BeanUtil工具Hutool CglibUtil
: Hutool提供的Cglib工具、基于Cglib BeanCopier、ASM字节码拷贝Spring BeanUtils
: Spring官方提供的BeanUtils、基于反射Apache BeanUtils
: 基于反射Orkia
: 基于javassist类库生成Bean映射的字节码Dozer
: 基于反射、定制化属性映射、XML映射
# 实验设置
本次实验只针对各工具类最核心接口,为了进行公平性比较,测试时将对需要动态根据source、target创建拷贝对象的工具类(RpamisBeanUtils、MapStruct、BeanCopier、Jackson、Hutool BeanUtil、Hutool CglibUtil、Orkia、Dozer)进行实例缓存、同时对源数据进行缓存,尽可能展示核心拷贝接口的性能。实际上在日常开发过程中,开发者对于经常使用的工具类也会选择用static final修饰,或采用诸如Map等进行实例缓存。或许是碍于需要每个给对比工具类增加缓存操作的工作量,在调研的文章中很少有考虑对实例进行缓存的操作,造成比如BeanCopier实验效果和MapStruct差异过大等问题。
在JMH中我们可以通过@State(Scope.Benchmark)
+@Setup(Level.Trial)
注解轻松实现在基准测试开始前的缓存初始化
# 基准参数设置
实验环境
提示
实验过程中应确保CPU拉满切避免发生降频现象导致实验结果不准确,发压机器理论和压力测试机器不为同一个,这里为了简单起见,在本机上进行直接压测
jmhVersion
:1.29
IntelliJ IDEA 2021.2.2
jdkVersion
:1.8.0_351
CPU
:Intel(R) Core(TM) i5-10600KF CPU @ 4.10GHz
Fork
:1
, 对于每个Benchmark Fork出一个线程,避免实验数据倾斜BenchmarkModel
:Mode.Throughput
, 采用吞吐量作为衡量指标Warmup
:3
, JIT预热3次之后进入正式测试Measurement
:iterations=10、time=5
, 每个Benchmark迭代10次,每次迭代5秒OutputTimeUnit
:TimeUnit.MILLISECONDS
, 吞吐量时间单位ops/msThreads
:10
, 生成10个线程进行发压
# 实验对象
本次实验有2组对象
- 简单类型对象DataBaseDO、DataBaseVO,简单类型仅有5个字段;
- 复杂类型对象DbDO、DbVo、MockOne、MockTwo,复杂类型对象中包含108个字段,且字段中存在MockOne、MockTwo对象,在MockOne中包含其自身的嵌套子集
List<MockOne>
# 实验结果
结果中Score表示测试的吞吐量,Error表示测试结果的平均差
- 程序运行结果
简单对象
Benchmark Mode Cnt Score Error Units
BenchmarkTestSimple.testApacheBeanUtils thrpt 10 1014.681 ± 5.442 ops/ms
BenchmarkTestSimple.testBeanCopier thrpt 10 341581.539 ± 668.458 ops/ms
BenchmarkTestSimple.testDozerMapping thrpt 10 1444.746 ± 6.486 ops/ms
BenchmarkTestSimple.testFastJson thrpt 10 9816.492 ± 64.882 ops/ms
BenchmarkTestSimple.testGetSet thrpt 10 341429.391 ± 407.244 ops/ms
BenchmarkTestSimple.testHutoolBeanUtil thrpt 10 1201.178 ± 14.053 ops/ms
BenchmarkTestSimple.testHutoolCglibUtil thrpt 10 340730.983 ± 757.836 ops/ms
BenchmarkTestSimple.testJackSon thrpt 10 7333.661 ± 36.987 ops/ms
BenchmarkTestSimple.testMapStruct thrpt 10 341577.692 ± 487.573 ops/ms
BenchmarkTestSimple.testOrikaMapper thrpt 10 2377.357 ± 3.422 ops/ms
BenchmarkTestSimple.testRpasBeanUtils thrpt 10 340737.565 ± 774.559 ops/ms
BenchmarkTestSimple.testSpringBeanUtils thrpt 10 1949.802 ± 2.807 ops/ms
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复杂对象
Benchmark Mode Cnt Score Error Units
BenchmarkTestComplex.testApacheBeanUtils thrpt 10 34.609 ± 0.405 ops/ms
BenchmarkTestComplex.testBeanCopier thrpt 10 24113.092 ± 127.129 ops/ms
BenchmarkTestComplex.testDozerMapping thrpt 10 96.133 ± 0.676 ops/ms
BenchmarkTestComplex.testFastJson thrpt 10 226.692 ± 1.215 ops/ms
BenchmarkTestComplex.testGetSet thrpt 10 24200.668 ± 43.997 ops/ms
BenchmarkTestComplex.testHutoolBeanUtil thrpt 10 68.630 ± 0.161 ops/ms
BenchmarkTestComplex.testHutoolCglibUtil thrpt 10 24147.446 ± 80.792 ops/ms
BenchmarkTestComplex.testJackSon thrpt 10 256.080 ± 2.660 ops/ms
BenchmarkTestComplex.testMapStruct thrpt 10 24111.832 ± 100.456 ops/ms
BenchmarkTestComplex.testOrikaMapper thrpt 10 1775.526 ± 1.818 ops/ms
BenchmarkTestComplex.testRpasBeanUtils thrpt 10 24109.377 ± 160.851 ops/ms
BenchmarkTestComplex.testSpringBeanUtils thrpt 10 94.354 ± 0.694 ops/ms
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 数值可视化
简单对象
从实验结果中我们可以看出BeanCopier
、MapStruct
和原生get/set
效率类似,吞吐量都很接近。
对于本文封装的RpamisBeanUtils
以及热门的Hutool CglibUtil
,两者效率近似,同时也离get/set
很接近,本质上这两款均基于BeanCopier封装,其主要性能损耗在弱引用的Map
缓存上。
同时以上4款工具,在平均差的表现也相对稳定。
对于知名的2组JSON工具类,由于其本身定位不为高频Bean拷贝而设计,所以2者的效率对比前者差出好几倍。FastJson
在这种场景下也明显快于Jackson
。
Orika
虽然采用了字节码技术,但由于其是深拷贝,需要创建新对象的原因,其效率也不尽人意。
其余的知名解决方案SpringBeanUtils
、Dozer
、ApacheBeanUtils
由于采用反射+深拷贝的原因,其效率严重低下。
Hutool
零依赖自研的BeanUtil
,在本轮测试结果中同样也存在效率低下的问题。
复杂对象
不同于简单对象测试,对于复杂对象的拷贝尤其考验拷贝工具类的性能,毕竟在拷贝场景中,我们不仅仅只有简单的对象。更有嵌套、多字段、多类型等复杂情况。
从实验结果中可以看出在简单对象排名前5的工具,在复杂对象的拷贝场景下依旧经受住了考验,这5个之间的排名波动可以理解为测试结果的误差性。
继续往下观察,我们可以发现在上一轮实验中,表现比其他好的2组JSON工具类性能出现了明显的下滑,原本高于JackSon
吞吐量的FastJson
,在本轮测试中屈居后位。
而Orika
却在复杂对象拷贝中稳定住了他的位置。
排名最后的4个工具依旧如简单对象拷贝排名类似,性能均很差。
- CPU频率图
# 结论
通过两组不同类型的对象,我们对12款工具进行了压测实验,最后结果表示BeanCopier
和MapStruct
依旧是市场中最顶级的两款工具类,两者均拥有相同于原生get/set
的性能,在使用时需要考虑使用缓存,两者均是高频Bean拷贝的工具首选。Hutool CglibUtil
提供了开箱即用的基于BeanCopier
的拷贝工具,如果没有特殊需求,又不想自己写工具类代码,也是强力的候选工具。如果Hutool
提供的工具类满足不了项目,可以选择本文中RpamisBeanUtils
,基于Spring
的弱引用ConcurrentReferenceHashMap
map,和缓存Cglib BeanCopier
或MapStruct
构建工具类,站在巨人的肩膀上,开发者也能快速的构建出适配项目且高性能的工具。同时在该场景下,我们应该避免使用其余基于反射、序列化等技术做出的工具,即使他们已经很出名。
# 附录
- 如果你的拷贝类中
get/set
含有特殊操作,以上主流的5款高性能的拷贝工具均会无法拷贝对应字段的值,其本质上是由于拷贝本身依赖于干净的get/set
方法。此时基于反射的工具,例如SpringBeanUtils
能够对这种特殊操作的实体进行拷贝,本质上是因为反射拷贝不需要依赖get/set
只需要反射获取字段动态赋值即可,但代价是性能十分低下。建议实体对象中,尽量不修改原始get/set
,如有实体类特殊需求,采用和get/set
生成方法不重名方法。 - 高性能拷贝的基石是浅拷贝,请确保拷贝后不再对源对象
source
进行修改,即拷贝时机发生在必要的转换时,如Controller层返回给前端VO,数据库层对象DO出库给各个接口使用返回DTO,因为源对象source
的赋值改变会引起目标对象target
的值变化,拷贝时本身是传递实体引用,如有特殊深拷贝需要可以了解MapStruct
的@DeepClone
- BeanCopier和MapStruct都是顶尖的工具,在源对象
source
和目标对象target
字段类型不同,但字段名相同时。可以采用BeanCopier的Converter
定义转换规则,或采用MapStruct的@mapping
注解。通常而言MapStruct更为强大,编译期生成get/set让人更加放心,但缺点就是基本的转换也需要写interface
,而BeanCopier不需要这点,仅在特殊转换时需要写Converter
。
压测核心代码
简单对象
package com.benym.benchmark.test;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.extra.cglib.BeanCopierCache;
import com.alibaba.fastjson.JSON;
import com.benym.benchmark.test.interfaces.MapStructMapper;
import com.benym.benchmark.test.model.complex.DbDO;
import com.benym.benchmark.test.model.complex.DbVO;
import com.benym.benchmark.test.model.simple.DataBaseDO;
import com.benym.benchmark.test.model.simple.DataBaseVO;
import com.benym.benchmark.test.service.ModelService;
import com.benym.benchmark.test.utils.RpamisBeanUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import net.sf.cglib.beans.BeanCopier;
import org.dozer.DozerBeanMapper;
import org.mapstruct.factory.Mappers;
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 org.springframework.beans.BeanUtils;
import java.util.concurrent.TimeUnit;
/**
* 简单对象拷贝基准测试
*
* @author: benym
*/
@Fork(1) // Fork 1个进程进行测试
@BenchmarkMode(Mode.Throughput) // 吞吐量
@Warmup(iterations = 3) // JIT预热
@Measurement(iterations = 10, time = 5) // 迭代10次,每次5s
@OutputTimeUnit(TimeUnit.MILLISECONDS) // 结果所使用的时间单位
@Threads(10) // 线程10个
public class BenchmarkTestSimple {
/**
* 作用域为本次JMH测试,线程共享
* <p>
* 初始化source数据集
*/
@State(Scope.Benchmark)
public static class GenerateModel {
DataBaseDO dataBaseModel;
// 初始化
@Setup(Level.Trial)
public void prepare() {
dataBaseModel = new ModelService().get();
}
}
/**
* 初始化Orika
*/
@State(Scope.Benchmark)
public static class OrikaMapper {
MapperFactory mapperFactory;
@Setup(Level.Trial)
public void prepare() {
mapperFactory = new DefaultMapperFactory.Builder().build();
}
}
/**
* 初始化Dozer
*/
@State(Scope.Benchmark)
public static class DozerMapper {
DozerBeanMapper mapper;
@Setup(Level.Trial)
public void prepare() {
mapper = new DozerBeanMapper();
}
}
@State(Scope.Benchmark)
public static class RpasBeanUtilsInit {
BeanCopier copier;
@Setup(Level.Trial)
public void prepare() {
copier = RpamisBeanUtils.getBeanCopierWithNoConverter(DataBaseDO.class, DataBaseVO.class);
}
}
/**
* 初始化BeanCopier
*/
@State(Scope.Benchmark)
public static class BeanCopierInit {
BeanCopier copier;
@Setup(Level.Trial)
public void prepare() {
copier = BeanCopier.create(DataBaseDO.class, DataBaseVO.class, false);
}
}
/**
* 初始化MapStruct
*/
@State(Scope.Benchmark)
public static class MapStructInit {
MapStructMapper mapStructMapper;
@Setup(Level.Trial)
public void prepare() {
mapStructMapper = Mappers.getMapper(MapStructMapper.class);
}
}
/**
* 初始化Objectmapper
*/
@State(Scope.Benchmark)
public static class ObjectMapperInit {
ObjectMapper objectMapper;
@Setup(Level.Trial)
public void prepare() {
objectMapper = new ObjectMapper();
}
}
/**
* 初始化hutool cglibUtil
*/
@State(Scope.Benchmark)
public static class HutoolCglibInit {
BeanCopier copier;
@Setup(Level.Trial)
public void prepare(){
copier = BeanCopierCache.INSTANCE.get(DataBaseDO.class, DataBaseVO.class, null);
}
}
/**
* get/set 基准测试
*
* @param generateModel source
* @return target
* @throws Exception
*/
@Benchmark
public DataBaseVO testGetSet(GenerateModel generateModel) throws Exception {
DataBaseVO dataBaseVO = new DataBaseVO();
DataBaseDO dataBaseModel = generateModel.dataBaseModel;
dataBaseVO.setAge(dataBaseModel.getAge());
dataBaseVO.setName(dataBaseModel.getName());
dataBaseVO.setTime(dataBaseModel.getTime());
dataBaseVO.setYear(dataBaseModel.getYear());
dataBaseVO.setOtherTime(dataBaseModel.getOtherTime());
return dataBaseVO;
}
/**
* RpasBeanUtils基准测试
*
* @param generateModel source
* @param init 初始化copier
* @return target
* @throws Exception
*/
@Benchmark
public DataBaseVO testRpasBeanUtils(GenerateModel generateModel, RpasBeanUtilsInit init) throws Exception {
DataBaseVO dataBaseVO = new DataBaseVO();
init.copier.copy(generateModel.dataBaseModel, dataBaseVO, null);
return dataBaseVO;
}
/**
* MapStruct基准测试
*
* @param generateModel source
* @param init 初始化的mapper
* @return target
* @throws Exception
*/
@Benchmark
public DataBaseVO testMapStruct(GenerateModel generateModel, MapStructInit init) throws Exception {
DataBaseVO dataBaseVO = init.mapStructMapper.copy(generateModel.dataBaseModel);
return dataBaseVO;
}
/**
* BeanCopier基准测试
*
* @param generateModel source
* @param beanCopier 初始化的BeanCopier
* @return target
* @throws Exception
*/
@Benchmark
public DataBaseVO testBeanCopier(GenerateModel generateModel, BeanCopierInit beanCopier) throws Exception {
BeanCopier copier = beanCopier.copier;
DataBaseVO dataBaseVO = new DataBaseVO();
copier.copy(generateModel.dataBaseModel, dataBaseVO, null);
return dataBaseVO;
}
/**
* Jackson objectMapper基准测试
*
* @param generateModel source
* @param init 初始化的ObjectMapper
* @return target
* @throws Exception
*/
@Benchmark
public DataBaseVO testJackSon(GenerateModel generateModel, ObjectMapperInit init) throws Exception {
String str = init.objectMapper.writeValueAsString(generateModel.dataBaseModel);
DataBaseVO dataBaseVO = init.objectMapper.readValue(str, DataBaseVO.class);
return dataBaseVO;
}
/**
* FastJson基准测试
*
* @param generateModel source
* @return target
* @throws Exception
*/
@Benchmark
public DataBaseVO testFastJson(GenerateModel generateModel) throws Exception {
String str = JSON.toJSONString(generateModel.dataBaseModel);
return JSON.parseObject(str, DataBaseVO.class);
}
/**
* Hutool BeanUtil基准测试
*
* @param generateModel source
* @return target
* @throws Exception
*/
@Benchmark
public DataBaseVO testHutoolBeanUtil(GenerateModel generateModel) throws Exception {
DataBaseVO dataBaseVO = new DataBaseVO();
BeanUtil.copyProperties(generateModel.dataBaseModel, dataBaseVO);
return dataBaseVO;
}
/**
* Hutool CglibUtil基准测试
*
* @param generateModel source
* @return target
* @throws Exception
*/
@Benchmark
public DataBaseVO testHutoolCglibUtil(GenerateModel generateModel, HutoolCglibInit init) throws Exception {
DataBaseVO dataBaseVO = new DataBaseVO();
init.copier.copy(generateModel.dataBaseModel, dataBaseVO, null);
return dataBaseVO;
}
/**
* SpringBeanUtils基准测试
*
* @param generateModel source
* @return target
* @throws Exception
*/
@Benchmark
public DataBaseVO testSpringBeanUtils(GenerateModel generateModel) throws Exception {
DataBaseVO dataBaseVO = new DataBaseVO();
BeanUtils.copyProperties(generateModel.dataBaseModel, dataBaseVO);
return dataBaseVO;
}
/**
* Apache BeanUtils基准测试
*
* @param generateModel source
* @return target
* @throws Exception
*/
@Benchmark
public DataBaseVO testApacheBeanUtils(GenerateModel generateModel) throws Exception {
DataBaseVO dataBaseVO = new DataBaseVO();
org.apache.commons.beanutils.BeanUtils.copyProperties(dataBaseVO, generateModel.dataBaseModel);
return dataBaseVO;
}
/**
* Orika基准测试
*
* @param generateModel source
* @param orikaMapper 初始化orika
* @return target
* @throws Exception
*/
@Benchmark
public DataBaseVO testOrikaMapper(GenerateModel generateModel, OrikaMapper orikaMapper) throws Exception {
MapperFacade mapperFacade = orikaMapper.mapperFactory.getMapperFacade();
DataBaseVO dataBaseVO = mapperFacade.map(generateModel.dataBaseModel, DataBaseVO.class);
return dataBaseVO;
}
/**
* Dozer基准测试
*
* @param generateModel source
* @param dozerMapper 初始化dozer
* @return target
* @throws Exception
*/
@Benchmark
public DataBaseVO testDozerMapping(GenerateModel generateModel, DozerMapper dozerMapper) throws Exception {
DataBaseVO dataBaseVO = dozerMapper.mapper.map(generateModel.dataBaseModel, DataBaseVO.class);
return dataBaseVO;
}
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder()
.include(BenchmarkTestSimple.class.getSimpleName())
.result("result-simple.json")
.resultFormat(ResultFormatType.JSON)
.build();
new Runner(options).run();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
复杂对象
package com.benym.benchmark.test;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.extra.cglib.BeanCopierCache;
import com.alibaba.fastjson.JSON;
import com.benym.benchmark.test.interfaces.MapStructMapperComplex;
import com.benym.benchmark.test.model.complex.DbDO;
import com.benym.benchmark.test.model.complex.DbVO;
import com.benym.benchmark.test.model.simple.DataBaseVO;
import com.benym.benchmark.test.service.ModelService;
import com.benym.benchmark.test.utils.RpamisBeanUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import net.sf.cglib.beans.BeanCopier;
import org.dozer.DozerBeanMapper;
import org.mapstruct.factory.Mappers;
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 org.springframework.beans.BeanUtils;
import java.util.concurrent.TimeUnit;
/**
* 复杂对象拷贝基准测试
*
* @author: benym
*/
@Fork(1) // Fork 1个进程进行测试
@BenchmarkMode(Mode.Throughput) // 吞吐量
@Warmup(iterations = 3) // JIT预热
@Measurement(iterations = 10, time = 5) // 迭代10次,每次5s
@OutputTimeUnit(TimeUnit.MILLISECONDS) // 结果所使用的时间单位
@Threads(10) // 线程10个
public class BenchmarkTestComplex {
/**
* 作用域为本次JMH测试,线程共享
* <p>
* 初始化source数据集
*/
@State(Scope.Benchmark)
public static class GenerateModel {
DbDO dbDo;
// 初始化
@Setup(Level.Trial)
public void prepare() {
dbDo = new ModelService().getComplex();
}
}
/**
* 初始化Orika
*/
@State(Scope.Benchmark)
public static class OrikaMapper {
MapperFactory mapperFactory;
@Setup(Level.Trial)
public void prepare() {
mapperFactory = new DefaultMapperFactory.Builder().build();
}
}
/**
* 初始化Dozer
*/
@State(Scope.Benchmark)
public static class DozerMapper {
DozerBeanMapper mapper;
@Setup(Level.Trial)
public void prepare() {
mapper = new DozerBeanMapper();
}
}
@State(Scope.Benchmark)
public static class RpasBeanUtilsInit {
BeanCopier copier;
@Setup(Level.Trial)
public void prepare() {
copier = RpamisBeanUtils.getBeanCopierWithNoConverter(DbDO.class, DbVO.class);
}
}
/**
* 初始化BeanCopier
*/
@State(Scope.Benchmark)
public static class BeanCopierInit {
BeanCopier copier;
@Setup(Level.Trial)
public void prepare() {
copier = BeanCopier.create(DbDO.class, DbVO.class, false);
}
}
/**
* 初始化MapStruct
*/
@State(Scope.Benchmark)
public static class MapStructInit {
MapStructMapperComplex mapStructMapper;
@Setup(Level.Trial)
public void prepare() {
mapStructMapper = Mappers.getMapper(MapStructMapperComplex.class);
}
}
/**
* 初始化Objectmapper
*/
@State(Scope.Benchmark)
public static class ObjectMapperInit {
ObjectMapper objectMapper;
@Setup(Level.Trial)
public void prepare() {
objectMapper = new ObjectMapper();
}
}
/**
* 初始化hutool cglibUtil
*/
@State(Scope.Benchmark)
public static class HutoolCglibInit {
BeanCopier copier;
@Setup(Level.Trial)
public void prepare(){
copier = BeanCopierCache.INSTANCE.get(DbDO.class, DbVO.class, null);
}
}
/**
* get/set 基准测试
*
* @param generateModel source
* @return target
* @throws Exception
*/
@Benchmark
public DbVO testGetSet(GenerateModel generateModel) throws Exception {
DbVO DbVO = new DbVO();
DbDO dbDo = generateModel.dbDo;
DbVO.setAge(dbDo.getAge());
DbVO.setName(dbDo.getName());
DbVO.setTime(dbDo.getTime());
DbVO.setYear(dbDo.getYear());
DbVO.setMockModelOne(dbDo.getMockModelOne());
DbVO.setMockModelTwo(dbDo.getMockModelTwo());
DbVO.setOtherTime(dbDo.getOtherTime());
DbVO.setField00(dbDo.getField00());
DbVO.setField01(dbDo.getField01());
DbVO.setField02(dbDo.getField02());
DbVO.setField03(dbDo.getField03());
DbVO.setField04(dbDo.getField04());
DbVO.setField05(dbDo.getField05());
DbVO.setField06(dbDo.getField06());
DbVO.setField07(dbDo.getField07());
DbVO.setField08(dbDo.getField08());
DbVO.setField09(dbDo.getField09());
DbVO.setField10(dbDo.getField10());
DbVO.setField11(dbDo.getField11());
DbVO.setField12(dbDo.getField12());
DbVO.setField13(dbDo.getField13());
DbVO.setField14(dbDo.getField14());
DbVO.setField15(dbDo.getField15());
DbVO.setField16(dbDo.getField16());
DbVO.setField17(dbDo.getField17());
DbVO.setField18(dbDo.getField18());
DbVO.setField19(dbDo.getField19());
DbVO.setField20(dbDo.getField20());
DbVO.setField21(dbDo.getField21());
DbVO.setField22(dbDo.getField22());
DbVO.setField23(dbDo.getField23());
DbVO.setField24(dbDo.getField24());
DbVO.setField25(dbDo.getField25());
DbVO.setField26(dbDo.getField26());
DbVO.setField27(dbDo.getField27());
DbVO.setField28(dbDo.getField28());
DbVO.setField29(dbDo.getField29());
DbVO.setField30(dbDo.getField30());
DbVO.setField31(dbDo.getField31());
DbVO.setField32(dbDo.getField32());
DbVO.setField33(dbDo.getField33());
DbVO.setField34(dbDo.getField34());
DbVO.setField35(dbDo.getField35());
DbVO.setField36(dbDo.getField36());
DbVO.setField37(dbDo.getField37());
DbVO.setField38(dbDo.getField38());
DbVO.setField39(dbDo.getField39());
DbVO.setField40(dbDo.getField40());
DbVO.setField41(dbDo.getField41());
DbVO.setField42(dbDo.getField42());
DbVO.setField43(dbDo.getField43());
DbVO.setField44(dbDo.getField44());
DbVO.setField45(dbDo.getField45());
DbVO.setField46(dbDo.getField46());
DbVO.setField47(dbDo.getField47());
DbVO.setField48(dbDo.getField48());
DbVO.setField49(dbDo.getField49());
DbVO.setField50(dbDo.getField50());
DbVO.setField51(dbDo.getField51());
DbVO.setField52(dbDo.getField52());
DbVO.setField53(dbDo.getField53());
DbVO.setField54(dbDo.getField54());
DbVO.setField55(dbDo.getField55());
DbVO.setField56(dbDo.getField56());
DbVO.setField57(dbDo.getField57());
DbVO.setField58(dbDo.getField58());
DbVO.setField59(dbDo.getField59());
DbVO.setField60(dbDo.getField60());
DbVO.setField61(dbDo.getField61());
DbVO.setField62(dbDo.getField62());
DbVO.setField63(dbDo.getField63());
DbVO.setField64(dbDo.getField64());
DbVO.setField65(dbDo.getField65());
DbVO.setField66(dbDo.getField66());
DbVO.setField67(dbDo.getField67());
DbVO.setField68(dbDo.getField68());
DbVO.setField69(dbDo.getField69());
DbVO.setField70(dbDo.getField70());
DbVO.setField71(dbDo.getField71());
DbVO.setField72(dbDo.getField72());
DbVO.setField73(dbDo.getField73());
DbVO.setField74(dbDo.getField74());
DbVO.setField75(dbDo.getField75());
DbVO.setField76(dbDo.getField76());
DbVO.setField77(dbDo.getField77());
DbVO.setField78(dbDo.getField78());
DbVO.setField79(dbDo.getField79());
DbVO.setField80(dbDo.getField80());
DbVO.setField81(dbDo.getField81());
DbVO.setField82(dbDo.getField82());
DbVO.setField83(dbDo.getField83());
DbVO.setField84(dbDo.getField84());
DbVO.setField85(dbDo.getField85());
DbVO.setField86(dbDo.getField86());
DbVO.setField87(dbDo.getField87());
DbVO.setField88(dbDo.getField88());
DbVO.setField89(dbDo.getField89());
DbVO.setField90(dbDo.getField90());
DbVO.setField91(dbDo.getField91());
DbVO.setField92(dbDo.getField92());
DbVO.setField93(dbDo.getField93());
DbVO.setField94(dbDo.getField94());
DbVO.setField95(dbDo.getField95());
DbVO.setField96(dbDo.getField96());
DbVO.setField97(dbDo.getField97());
DbVO.setField98(dbDo.getField98());
DbVO.setField99(dbDo.getField99());
DbVO.setField100(dbDo.getField100());
return DbVO;
}
/**
* RpasBeanUtils基准测试
*
* @param generateModel source
* @param init 初始化copier
* @return target
* @throws Exception
*/
@Benchmark
public DbVO testRpasBeanUtils(GenerateModel generateModel, RpasBeanUtilsInit init) throws Exception {
DbVO dbVO = new DbVO();
init.copier.copy(generateModel.dbDo, dbVO, null);
return dbVO;
}
/**
* MapStruct基准测试
*
* @param generateModel source
* @param init 初始化的mapper
* @return target
* @throws Exception
*/
@Benchmark
public DbVO testMapStruct(GenerateModel generateModel, MapStructInit init) throws Exception {
DbVO DbVO = init.mapStructMapper.copy(generateModel.dbDo);
return DbVO;
}
/**
* BeanCopier基准测试
*
* @param generateModel source
* @param beanCopier 初始化的BeanCopier
* @return target
* @throws Exception
*/
@Benchmark
public DbVO testBeanCopier(GenerateModel generateModel, BeanCopierInit beanCopier) throws Exception {
BeanCopier copier = beanCopier.copier;
DbVO DbVO = new DbVO();
copier.copy(generateModel.dbDo, DbVO, null);
return DbVO;
}
/**
* Jackson objectMapper基准测试
*
* @param generateModel source
* @param init 初始化的ObjectMapper
* @return target
* @throws Exception
*/
@Benchmark
public DbVO testJackSon(GenerateModel generateModel, ObjectMapperInit init) throws Exception {
String str = init.objectMapper.writeValueAsString(generateModel.dbDo);
DbVO dbVO = init.objectMapper.readValue(str, DbVO.class);
return dbVO;
}
/**
* FastJson基准测试
*
* @param generateModel source
* @return target
* @throws Exception
*/
@Benchmark
public DbVO testFastJson(GenerateModel generateModel) throws Exception {
String str = JSON.toJSONString(generateModel.dbDo);
return JSON.parseObject(str, DbVO.class);
}
/**
* Hutool BeanUtil基准测试
*
* @param generateModel source
* @return target
* @throws Exception
*/
@Benchmark
public DbVO testHutoolBeanUtil(GenerateModel generateModel) throws Exception {
DbVO DbVO = new DbVO();
BeanUtil.copyProperties(generateModel.dbDo, DbVO);
return DbVO;
}
/**
* Hutool CglibUtil基准测试
*
* @param generateModel source
* @return target
* @throws Exception
*/
@Benchmark
public DbVO testHutoolCglibUtil(GenerateModel generateModel, HutoolCglibInit init) throws Exception {
DbVO dbVO = new DbVO();
init.copier.copy(generateModel.dbDo, dbVO, null);
return dbVO;
}
/**
* SpringBeanUtils基准测试
*
* @param generateModel source
* @return target
* @throws Exception
*/
@Benchmark
public DbVO testSpringBeanUtils(GenerateModel generateModel) throws Exception {
DbVO DbVO = new DbVO();
BeanUtils.copyProperties(generateModel.dbDo, DbVO);
return DbVO;
}
/**
* Apache BeanUtils基准测试
*
* @param generateModel source
* @return target
* @throws Exception
*/
@Benchmark
public DbVO testApacheBeanUtils(GenerateModel generateModel) throws Exception {
DbVO DbVO = new DbVO();
org.apache.commons.beanutils.BeanUtils.copyProperties(DbVO, generateModel.dbDo);
return DbVO;
}
/**
* Orika基准测试
*
* @param generateModel source
* @param orikaMapper 初始化orika
* @return target
* @throws Exception
*/
@Benchmark
public DbVO testOrikaMapper(GenerateModel generateModel, OrikaMapper orikaMapper) throws Exception {
MapperFacade mapperFacade = orikaMapper.mapperFactory.getMapperFacade();
DbVO DbVO = mapperFacade.map(generateModel.dbDo, DbVO.class);
return DbVO;
}
/**
* Dozer基准测试
*
* @param generateModel source
* @param dozerMapper 初始化dozer
* @return target
* @throws Exception
*/
@Benchmark
public DbVO testDozerMapping(GenerateModel generateModel, DozerMapper dozerMapper) throws Exception {
DbVO DbVO = dozerMapper.mapper.map(generateModel.dbDo, DbVO.class);
return DbVO;
}
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder()
.include(BenchmarkTestComplex.class.getSimpleName())
.result("result-complex.json")
.resultFormat(ResultFormatType.JSON)
.build();
new Runner(options).run();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
- 01
- SpringCache基本配置类05-16
- 03
- Rpamis-security-原理解析12-13