getProperty操作的性能测试与优化

简介: 经典的Apache BeanUtils的性能一直令人诟病,本文针对各种getProperty的操作进行性能测试,并探索一种高性能的getProperty方案。

1. 性能对比测试

基于JMH(1.32版本)进行性能测试(机器:MacBook Pro 13,16GB内存,M1芯片;JDK版本:zulu-8,1.8.0_302),分别比较原生的getXXX(),Apache BeanUtils的getProperty(),基于原生反射机制的field.get()和unsafe的getObject(),测试代码如下:

packagecom.xycode.paramcheck.service.domain;
importlombok.Data;
importlombok.EqualsAndHashCode;
importlombok.ToString;
/*** @author: lianguang* @date: 2021/8/15*/@Data@EqualsAndHashCode(callSuper=true)
@ToString(callSuper=true)
publicclassDomainextendsBaseDomain {
privateIntegerage;
privateStringname;
publicDomain(Longid, Integerage, Stringname) {
super(id);
this.age=age;
this.name=name;
    }
}
packagecom.xycode.paramcheck.benchmark;
importcom.xycode.paramcheck.service.domain.Domain;
importorg.apache.commons.beanutils.BeanUtils;
importorg.junit.jupiter.api.BeforeEach;
importorg.junit.jupiter.api.Test;
importorg.openjdk.jmh.annotations.*;
importorg.openjdk.jmh.runner.Runner;
importorg.openjdk.jmh.runner.RunnerException;
importorg.openjdk.jmh.runner.options.Options;
importorg.openjdk.jmh.runner.options.OptionsBuilder;
importsun.misc.Unsafe;
importjava.lang.reflect.Field;
importjava.util.Map;
importjava.util.concurrent.ConcurrentHashMap;
importjava.util.concurrent.TimeUnit;
/*** @author: lianguang* @date: 2021/11/3*/@Warmup(iterations=3)
@Measurement(iterations=5, time=5)
@Fork(2)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
publicclassBeanGetterBenchMark {
publicUnsafeunsafe;
publicUnsafegetUnsafe() {
try {
Fieldfield=Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
        } catch (NoSuchFieldException|IllegalAccessExceptione) {
e.printStackTrace();
        }
returnnull;
    }
privateDomainsource=newDomain(1000L, 100, "source");
@Setup@BeforeEachpublicvoidinit() throwsException {
unsafe=getUnsafe();
    }
@Benchmark@BenchmarkMode(Mode.Throughput)
publicvoidtestApacheBeanUtilsGetProperty() throwsException {
BeanUtils.getProperty(source, "id");
    }
@Benchmark@BenchmarkMode(Mode.Throughput)
publicvoidgetPropertyReflect() throwsException {
Fieldafield=Domain.class.getSuperclass().getDeclaredField("id");
afield.setAccessible(true);
afield.get(source);
    }
@Benchmark@BenchmarkMode(Mode.Throughput)
publicvoidgetPropertyUnsafe() throwsException {
longaFieldOffset=unsafe.objectFieldOffset(Domain.class.getSuperclass().getDeclaredField("id"));
unsafe.getObject(source, aFieldOffset);
    }
@Benchmark@BenchmarkMode(Mode.Throughput)
publicvoidtestNativeGetter() {
source.getId();
    }
publicstaticvoidmain(String[] args) throwsRunnerException {
Optionsoptions=newOptionsBuilder()
                .include(BeanGetterBenchMark.class.getSimpleName())
                .build();
newRunner(options).run();
    }

测试结果:

Benchmark                                            Mode  Cnt     Score    Error   Units
BeanGetterBenchMark.getPropertyReflect              thrpt   10     4.140 ±  0.019  ops/us
BeanGetterBenchMark.getPropertyUnsafe               thrpt   10     2.914 ±  0.126  ops/us
BeanGetterBenchMark.testApacheBeanUtilsGetProperty  thrpt   10     0.464 ±  0.014  ops/us
BeanGetterBenchMark.testNativeGetter                thrpt   10  1794.462 ± 14.348  ops/us

可见,Apache的BeanUtils的性能最差,原生反射的性能稍好一些,意外的是,unsafe方式性能低于原生反射方式。原生get的方式性能最好,是反射方式与unsafe方式性能的数百倍,有没有什么方法能够提升反射或unsafe的性能呢?稍加分析,反射方式和unsafe方式的过程大致都分为两个步骤,第一个步骤先获取属性的field,第二个步骤则根据field去获取属性值,又因为一个类的某个属性的field是相对不变的,由此可得一种提升性能的方式是将field缓存起来,当需要field时直接从缓存中获取,经过测试(在16GB,M1芯片的MacBook Pro13机器上),Java中Map的get性能大概在 200~300 ops/us,因此是有提升性能的空间的。

模拟缓存情况下的性能,测试代码如下:

packagecom.xycode.paramcheck.benchmark;
importcom.xycode.paramcheck.service.domain.Domain;
importorg.apache.commons.beanutils.BeanUtils;
importorg.junit.jupiter.api.BeforeEach;
importorg.junit.jupiter.api.Test;
importorg.openjdk.jmh.annotations.*;
importorg.openjdk.jmh.runner.Runner;
importorg.openjdk.jmh.runner.RunnerException;
importorg.openjdk.jmh.runner.options.Options;
importorg.openjdk.jmh.runner.options.OptionsBuilder;
importsun.misc.Unsafe;
importjava.lang.reflect.Field;
importjava.util.Map;
importjava.util.concurrent.ConcurrentHashMap;
importjava.util.concurrent.TimeUnit;
/*** @author: lianguang* @date: 2021/11/3*/@Warmup(iterations=3)
@Measurement(iterations=5, time=5)
@Fork(2)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
publicclassBeanGetterBenchMark {
publicUnsafeunsafe;
publicUnsafegetUnsafe() {
try {
Fieldfield=Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
        } catch (NoSuchFieldException|IllegalAccessExceptione) {
e.printStackTrace();
        }
returnnull;
    }
privateDomainsource=newDomain(1000L, 100, "source");
privateMap<String, Long>offsetMap=newConcurrentHashMap<>();
privateMap<String, Field>fieldMap=newConcurrentHashMap<>();
@Setuppublicvoidinit() throwsException {
unsafe=getUnsafe();
//模拟缓存优化Fieldfield=Domain.class.getSuperclass().getDeclaredField("id");
field.setAccessible(true);
fieldMap.put("id",field);
offsetMap.put("id",unsafe.objectFieldOffset(field));
    }
@Benchmark@BenchmarkMode(Mode.Throughput)
publicvoidtestApacheBeanUtilsGetProperty() throwsException {
BeanUtils.getProperty(source, "id");
    }
@Benchmark@BenchmarkMode(Mode.Throughput)
publicvoidgetPropertyReflectWithCache() throwsException {
fieldMap.get("id").get(source);
    }
@Benchmark@BenchmarkMode(Mode.Throughput)
@TestpublicvoidgetPropertyUnsafeWithCache() {
unsafe.getObject(source, offsetMap.get("id"));
    }
@Benchmark@BenchmarkMode(Mode.Throughput)
@TestpublicvoidtestNativeGetter() {
source.getId();
    }
publicstaticvoidmain(String[] args) throwsRunnerException {
Optionsoptions=newOptionsBuilder()
                .include(BeanGetterBenchMark.class.getSimpleName())
                .build();
newRunner(options).run();
    }
}

测试结果如下:

Benchmark(考虑到ConcurrentHashMap的开销)               Mode  Cnt     Score    Error   Units
BeanGetterBenchMark.getPropertyReflectWithCache     thrpt   10   175.756 ±  3.043  ops/us
BeanGetterBenchMark.getPropertyUnsafeWithCache      thrpt   10   258.211 ±  3.122  ops/us
BeanGetterBenchMark.testApacheBeanUtilsGetProperty  thrpt   10     0.435 ±  0.056  ops/us
BeanGetterBenchMark.testNativeGetter                thrpt   10  1771.469 ± 30.345  ops/us

可以看出,在命中缓存的情况下,反射方式与unsafe方式的性能得到了极大地提升,有一点比较有意思,unsafe方式在缓存情况下的性能超过了反射方式。因为实际上unsafe.getObject()是一个Native方法,其性能是相当优秀的,在fieldOffset是现成的基础上,unsafe.getObject()性能与原生的getXXX相当,经过测试,它们之间的性能对比如下:(甚至有时的测试结果高于原生的getXXX)

BeanUtilsBenchMark.testNativeGetter  1784.362 ± 10.617  ops/us
BeanUtilsBenchMark.testUnsafeGetter  1802.107 ±  4.319  ops/us

2. BeanOperationUtils的实现

通过以上的分析,加了缓存后能够极大地提升反射方式与unsafe方式的get的性能,并且在缓存下的unsafe方式性能反超原生反射方式,由此这里实现了一个工具类,代码如下所示:

packagecom.xycode.paramcheck.utils;
importjava.util.Objects;
/*** 缓存key** @author: lianguang* @date: 2021/11/3*/publicclassFieldCacheKey {
/*** 类对象*/privateClass<?>clazz;
/*** 类的属性名*/privateStringpropertyName;
publicFieldCacheKey(Class<?>clazz, StringpropertyName) {
this.clazz=clazz;
this.propertyName=propertyName;
    }
publicClass<?>getClazz() {
returnclazz;
    }
publicvoidsetClazz(Class<?>clazz) {
this.clazz=clazz;
    }
publicStringgetPropertyName() {
returnpropertyName;
    }
publicvoidsetPropertyName(StringpropertyName) {
this.propertyName=propertyName;
    }
@Overridepublicbooleanequals(Objecto) {
if (this==o) returntrue;
if (o==null||getClass() !=o.getClass()) returnfalse;
FieldCacheKeythat= (FieldCacheKey) o;
returnObjects.equals(clazz, that.clazz) &&Objects.equals(propertyName, that.propertyName);
    }
@OverridepublicinthashCode() {
returnObjects.hash(clazz, propertyName);
    }
}
packagecom.xycode.paramcheck.utils;
importsun.misc.Unsafe;
importjava.lang.reflect.Field;
importjava.util.Map;
importjava.util.Objects;
importjava.util.concurrent.ConcurrentHashMap;
/*** bean的相关操作** @author: lianguang* @date: 2021/11/3*/publicclassBeanOperationUtils {
privatestaticfinalUnsafeunsafe;
privatestaticfinalMap<FieldCacheKey, Long>fieldOffsetCache=newConcurrentHashMap<>(1024);
privatestaticfinalMap<FieldCacheKey, Field>fieldCache=newConcurrentHashMap<>(1024);
static {
unsafe=getUnsafe();
    }
privatestaticUnsafegetUnsafe() {
try {
Fieldfield=Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
        } catch (NoSuchFieldException|IllegalAccessExceptione) {
e.printStackTrace();
        }
returnnull;
    }
/*** 因为可能有嵌套属性的存在, 需要递归向上获取field*/privatestaticvoidcollectField(finalClass<?>clazz, finalStringname, finalField[] fields) throwsException {
if (Objects.equals(Object.class, clazz) ||Objects.nonNull(fields[0])) {
return;
        }
try {
fields[0] =clazz.getDeclaredField(name);
        } catch (Exceptionignored) {
        }
collectField(clazz.getSuperclass(), name, fields);
    }
/*** 反射方式获取属性值, 性能远高于Apache的BeanUtils.getProperty(), 低于命中缓存情况下的unsafe方式** @param clazz 类* @param name  属性名*/privatestaticFieldgetFieldWithCache(finalClass<?>clazz, finalStringname) throwsException {
Field[] fields=newField[1];
FieldCacheKeykey=newFieldCacheKey(clazz, name);
fields[0] =fieldCache.get(key);
if (Objects.isNull(fields[0])) {
collectField(clazz, name, fields);
if (Objects.nonNull(fields[0])) {
fields[0].setAccessible(true);
//设置缓存fieldCache.put(key, fields[0]);
            }
        }
returnfields[0];
    }
/*** 反射方式获取属性值, 性能远高于Apache的BeanUtils.getProperty(), 低于命中缓存情况下的unsafe方式** @param clazz 类* @param name  属性名*/privatestaticFieldgetField(finalClass<?>clazz, finalStringname) throwsException {
Field[] fields=newField[1];
fields[0] =null;
collectField(clazz, name, fields);
if (Objects.nonNull(fields[0])) {
fields[0].setAccessible(true);
        }
returnfields[0];
    }
/*** 检查对象中是否包含属性定义, 在进行getProperty()操作时, 需要先检查一下** @param clazz 类* @param name  属性名*/publicstaticbooleancontainsProperty(Class<?>clazz, Stringname) throwsException {
if (Objects.isNull(clazz) ||Objects.isNull(name)) {
returnfalse;
        }
Fieldfield=getFieldWithCache(clazz, name);
if (Objects.nonNull(field)) {
returntrue;
        } else {
returnfalse;
        }
    }
/*** 反射方式获取属性值(带缓存FieldCache)* notice: 在进行getProperty()操作时, 需要调用containsProperty先检查一下, 若存在,才调用getProperty()** @param bean 对象* @param name 属性名*/publicstaticObjectgetPropertyWithFieldCache(Objectbean, Stringname) throwsException {
if (Objects.isNull(bean) ||Objects.isNull(name)) {
returnnull;
        }
Fieldfield=getFieldWithCache(bean.getClass(), name);
if (Objects.nonNull(field)) {
returnfield.get(bean);
        } else {
returnnull;
        }
    }
/*** unsafe方式获取属性值(带fieldOffsetCache)** @param bean 对象* @param name 属性名* notice 在进行getProperty()操作时, 需要调用containsProperty先检查一下, 若存在,才调用getProperty()*/publicstaticObjectgetPropertyWithFieldOffsetCache(Objectbean, Stringname) throwsException {
if (Objects.isNull(bean) ||Objects.isNull(name)) {
returnnull;
        }
FieldCacheKeykey=newFieldCacheKey(bean.getClass(), name);
Longoffset=fieldOffsetCache.get(key);
if (Objects.isNull(offset)) {
//已经有fieldOffsetCache了,不用重复缓存了Fieldfield=getField(bean.getClass(), name);
//设置缓存fieldOffsetCache.put(key, unsafe.objectFieldOffset(field));
returnfield.get(bean);
        } else {
/*** unsafe.getObject()是native方法,性能与原生的object.getXXX()相差无几, 基于jmh的性能测试如下:*     BeanUtilsBenchMark.testNativeGetter  1784.362 ± 10.617  ops/us*     BeanUtilsBenchMark.testUnsafeGetter  1802.107 ±  4.319  ops/us*/returnunsafe.getObject(bean, offset);
        }
    }
}

3. BeanOperationUtils的性能测试

BeanOperationUtils的性能测试代码如下:

packagecom.xycode.paramcheck.benchmark;
importcom.xycode.paramcheck.service.domain.Domain;
importcom.xycode.paramcheck.utils.BeanOperationUtils;
importorg.openjdk.jmh.annotations.*;
importorg.openjdk.jmh.runner.Runner;
importorg.openjdk.jmh.runner.RunnerException;
importorg.openjdk.jmh.runner.options.Options;
importorg.openjdk.jmh.runner.options.OptionsBuilder;
importjava.util.concurrent.TimeUnit;
/*** @author: lianguang* @date: 2021/11/3*/@Warmup(iterations=3)
@Measurement(iterations=5, time=5)
@Fork(2)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
publicclassBeanOperationBenchmark {
privateDomainsource=newDomain(1000L, 100, "source");
@Setuppublicvoidinit() throwsException {
    }
@Benchmark@BenchmarkMode(Mode.Throughput)
publicvoidtestReflectGetterWithCache() throwsException {
//反射方式获取属性值BeanOperationUtils.getPropertyWithFieldCache(source, "id");
    }
@Benchmark@BenchmarkMode(Mode.Throughput)
publicvoidtestUnsafeGetterWithCache() throwsException {
//unsafe方式获取属性值BeanOperationUtils.getPropertyWithFieldOffsetCache(source, "id");
    }
publicstaticvoidmain(String[] args) throwsRunnerException {
Optionsoptions=newOptionsBuilder()
                .include(BeanOperationBenchmark.class.getSimpleName())
                .build();
newRunner(options).run();
    }
}

测试结果如下:

Benchmark                                   Mode  Cnt    Score   Error   Units
BeanOperationBenchmark.testReflectGetterWithCache   thrpt   10   86.110 ± 2.601  ops/us
BeanOperationBenchmark.testUnsafeGetterWithCache  thrpt   10  137.352 ± 2.046  ops/us

可见,BeanOperationUtils的性能远高于Apache BeanUtils的getProperty(),其中unsafe方式(命中fieldOffset缓存)的性能高于反射方式(命中field缓存)。

相关实践学习
通过性能测试PTS对云服务器ECS进行规格选择与性能压测
本文为您介绍如何利用性能测试PTS对云服务器ECS进行规格选择与性能压测。
目录
相关文章
|
23天前
|
安全 Linux 测试技术
提升龙蜥内核测试能力!探究持续性模糊测试优化实践
清华大学软件学院对Anolis OS使用靶向模糊测试方法将测试工作引向修改的代码,进而提高对业务代码的测试能力。
|
3月前
|
存储 测试技术 持续交付
自动化测试与持续集成/持续交付(CI/CD):优化软件开发流程的利器
自动化测试与持续集成/持续交付(CI/CD)是现代软件开发中至关重要的环节,通过将自动化测试与持续集成/持续交付相结合,可以实现开发流程的高效优化,提高软件质量和交付速度。本文将探讨自动化测试与CI/CD的概念、原理及其在软件开发中的重要性,以及如何实施这些技术以提升团队的协作效率和软件交付质量。
58 1
|
3月前
|
监控 数据可视化 Java
jvm性能调优实战 - 31从测试到上线_如何分析JVM运行状况及合理优化
jvm性能调优实战 - 31从测试到上线_如何分析JVM运行状况及合理优化
53 1
|
监控 NoSQL 前端开发
软件测试|Mongodb的分页优化及索引使用
软件测试|Mongodb的分页优化及索引使用
378 0
软件测试|Mongodb的分页优化及索引使用
|
1月前
|
敏捷开发 分布式计算 测试技术
深入理解软件测试中的自动化框架选择与优化策略
【2月更文挑战第29天】 在软件开发的生命周期中,测试环节扮演着至关重要的角色。随着敏捷开发和持续集成的普及,自动化测试成为确保软件质量和加快产品上市速度的关键手段。本文将探讨在构建自动化测试框架时面临的挑战,分析不同类型自动化框架的特点及其适用场景,并提出一系列优化策略,旨在帮助测试工程师提高测试效率,确保测试结果的准确性。
21 0
|
1月前
|
敏捷开发 分布式计算 数据管理
探索自动化测试在持续集成环境中的优化策略
【2月更文挑战第18天】 在高速迭代的软件开发过程中,自动化测试已成为确保产品质量和加快交付速度的关键。本文深入探讨了自动化测试在持续集成(CI)环境中面临的挑战,并提出了一系列优化策略。通过对测试流程、工具选择和测试数据管理等方面的细致分析,旨在为软件测试人员提供实用的改进方法,以提高自动化测试的效率和准确性。
|
8月前
|
Java Spring
优化测试执行顺序:解析 Java 中的 @Order 注解
在软件开发中,测试的执行顺序有时候是至关重要的,尤其是在涉及依赖关系或状态共享的情况下。Java 中的 `@Order` 注解为开发人员提供了一种简便的方式来控制测试方法的执行顺序,确保测试按照指定的顺序运行。本文将带您深入探索 Java 中的 `@Order` 注解,揭示其作用、用法以及在实际开发中的应用场景。
|
8月前
|
前端开发 算法 JavaScript
【简历优化平台开发教程-12】测试用模版简历!
【简历优化平台开发教程-12】测试用模版简历!
|
11月前
|
负载均衡 测试技术 应用服务中间件

热门文章

最新文章