本文正在参加「Java主题月 – Java开发实战」,详情查看:juejin.cn/post/696719…
这是我参与更文挑战的第8天,活动详情查看: 更文挑战
属性转换方法的性能测试
一般而言,我们会直接使用:
Object a = new Object();
...
a.setProperties1(B.get());
...
复制代码
来进行新对象的构建。
但当属性很多的时候,一大堆set就会显得业务代码非常地臃肿而且难以维护。
如果是属性名称相同的情况下,常用的解决方法,就是使用反射来代替这个过程:
BeanUtils.copyProperties(origin,dest);
复制代码
当然,JDK反射的性能就会比较差。
此时我们可以使用ASM反射,或者mapstruct的方式,来替换这个操作,达到更好的效果。
简单地说:
- ASM反射就是字节码层面的反射,性能优于JDK
- mapstruct使用freemark,对按照约定编写的接口,在构建的时候进行自动实现
我们通过JMH套件,来测试一下三者的工作效率。
测试类编写
pojo
为了简便,我们就使用两个相同的pojo。
@Data
public class SetterTestDest {
private int id;
private Integer idBoxing;
private String content;
}
@Data
public class SetterTestOrigin {
private int id;
private Integer idBoxing;
private String content;
}
复制代码
mapper
因为mapstruct需要提前编写接口,因此需要提前编写对应的转换接口。
根据参考2中的默认基类,我们实现一个继承的接口。
@Mapper(componentModel = "Spring",unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface SetterTestJMHMapper extends BaseMapping<SetterTestOrigin, SetterTestDest> {
SetterTestJMHMapper INSTANCE = Mappers.getMapper(SetterTestJMHMapper.class);
}
复制代码
测试类
为了测试的准确性,我们使用JMH来进行性能对比测试。
/**
* 测试set的几种便捷方法的性能差距:
* 1.反射
* 2.ASM
* 3.mapstruct
*/
@BenchmarkMode({Mode.Throughput})
@State(value = Scope.Thread)
@Timeout(time = 2,timeUnit = TimeUnit.MINUTES)
@Warmup(iterations = 3, time = 30,timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 3, time = 1,timeUnit = TimeUnit.MINUTES)
@OutputTimeUnit(TimeUnit.SECONDS)
@Threads(3)
public class SetterMethodTest {
static SetterTestJMHMapper mapStruct = SetterTestJMHMapper.INSTANCE;
public static List<SetterTestOrigin> origins = null;
static{
origins = new ArrayList<>(100000);
for (int i = 0; i < 100000; i++) {
SetterTestOrigin origin = new SetterTestOrigin();
origin.setId(i);
origin.setIdBoxing(i);
origin.setContent(String.valueOf(i));
origins.add(origin);
}
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(SetterMethodTest.class.getSimpleName())
.threads(3)
.forks(1)
.resultFormat(ResultFormatType.JSON).build();
new Runner(opt).run();
}
@Benchmark
public static void reflectSetter(){
List<SetterTestDest> res = new ArrayList<>();
for (SetterTestOrigin origin : origins) {
SetterTestDest dest = new SetterTestDest();
BeanUtils.copyProperties(origin,dest);
res.add(dest);
}
}
@Benchmark
public static void ASMSetter(){
List<SetterTestDest> res = new ArrayList<>();
FieldAccess originAccess = FieldAccess.get(SetterTestOrigin.class);
FieldAccess targetAccess = FieldAccess.get(SetterTestDest.class);
for (SetterTestOrigin origin : origins) {
SetterTestDest cur = new SetterTestDest();
for (String fieldName : originAccess.getFieldNames()) {
targetAccess.set(cur, fieldName,originAccess.get(origin,fieldName));
}
res.add(cur);
}
}
@Benchmark
public static void mapStructSetter(){
List<SetterTestDest> res = new ArrayList<>();
res = mapStruct.sourceToTarget(origins);
}
复制代码
测试结果:
Benchmark Mode Cnt Score Error Units
testFunc.SetterMethodTest.ASMSetter thrpt 3 1206.491 ± 412.765 ops/s
testFunc.SetterMethodTest.mapStructSetter thrpt 3 1586.778 ± 1161.509 ops/s
testFunc.SetterMethodTest.reflectSetter thrpt 3 187.888 ± 197.795 ops/s
复制代码
结论
- ASM性能比想象的还要好,性能大幅度优于通过反射进行的值转换
- 当然也有可能是因为我们的ASM反射代码,不像BeanUtils一样考虑了其他的细节
- mapStruct性能最好,误差比较大,需要考虑一下JDK层面上导致的误差
- 反射性能比前两者差的非常多,如果是使用自己实现的反射方法,可能性能会优化一些
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END