属性转换方法的性能测试

本文正在参加「Java主题月 – Java开发实战」,详情查看:juejin.cn/post/696719…

这是我参与更文挑战的第8天,活动详情查看: 更文挑战

属性转换方法的性能测试

一般而言,我们会直接使用:

Object a = new Object();
...
a.setProperties1(B.get());
...
复制代码

来进行新对象的构建。

但当属性很多的时候,一大堆set就会显得业务代码非常地臃肿而且难以维护。

如果是属性名称相同的情况下,常用的解决方法,就是使用反射来代替这个过程:

BeanUtils.copyProperties(origin,dest);
复制代码

当然,JDK反射的性能就会比较差。

此时我们可以使用ASM反射,或者mapstruct的方式,来替换这个操作,达到更好的效果。

简单地说:

我们通过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
喜欢就支持一下吧
点赞0 分享