getProperty操作的性能测试与优化

本文涉及的产品
性能测试 PTS,5000VUM额度
简介: 经典的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进行规格选择与性能压测。
目录
相关文章
|
6天前
|
监控 测试技术 持续交付
软件测试中的性能瓶颈分析与优化策略
性能瓶颈,如同潜伏于软件深处的隐形障碍,悄然阻碍着系统的流畅运行。本文旨在揭示这些瓶颈的形成机理,剖析其背后的复杂成因,并汇聚一系列针对性的优化策略,为软件开发者提供一套系统性的解决方案。
|
4月前
|
监控 测试技术 UED
软件测试中的性能瓶颈定位与优化策略
在软件开发的生命周期中,性能测试是确保产品质量的关键步骤之一。本文深入探讨了性能测试的重要性,并提出了一套系统的性能瓶颈定位与优化策略。通过分析现代软件系统中常见的性能问题,结合最新的研究成果和行业最佳实践,文章详细介绍了如何运用科学严谨的方法来识别和解决性能瓶颈。此外,本文还强调了逻辑严密的问题分析框架和数据驱动的决策过程对于提升软件性能的重要性。
|
12天前
|
监控 算法 测试技术
软件测试中的性能瓶颈分析与优化策略
本文旨在深入探讨软件测试过程中性能瓶颈的识别与优化方法。通过对性能瓶颈的概念、分类及其成因进行分析,结合实际案例,提出一套系统的性能瓶颈诊断流程和针对性的优化策略。文章首先概述了性能瓶颈的基本特征,随后详细介绍了内存泄漏、资源竞争、算法效率低下等常见瓶颈类型,并阐述了如何通过代码审查、性能监测工具以及负载测试等手段有效定位问题。最后,结合最佳实践,讨论了代码级优化、系统配置调整、架构改进等多方面的解决措施,旨在为软件开发和测试人员提供实用的性能优化指导。
|
22天前
|
关系型数据库 MySQL 测试技术
《性能测试》读书笔记_数据库优化
《性能测试》读书笔记_数据库优化
24 7
|
5天前
|
缓存 监控 算法
软件测试中的性能瓶颈定位与优化策略
性能瓶颈,如同隐藏在系统深处的“拦路虎”,悄无声息地制约着软件的表现。本文将揭示如何通过一系列科学方法,识别并消除这些障碍,从而显著提升软件性能,确保用户享受到流畅无阻的数字体验。
|
2月前
|
存储 人工智能 自然语言处理
知识库优化增强,支持多种数据类型、多种检索策略、召回测试 | Botnow上新
Botnow近期对其知识库功能进行了全面升级,显著提升了数据处理能力、检索效率及准确性。新版本支持多样化的数据格式,包括PDF、Word、TXT、Excel和CSV等文件,无需额外转换即可直接导入,极大地丰富了知识来源。此外,还新增了细致的文本分片管理和编辑功能,以及表格数据的结构化处理,使知识管理更为精细化。 同时,平台提供了多种检索策略,包括混合检索、语义检索和全文检索等,可根据具体需求灵活选择,有效解决了大模型幻觉问题,增强了专业领域的知识覆盖,从而显著提高了回复的准确性。这些改进广泛适用于客服咨询、知识问答等多种应用场景,极大提升了用户体验和交互质量。
58 4
|
2月前
|
测试技术
软件测试中的心理学:如何优化测试流程
【8月更文挑战第6天】本文深入探讨了软件测试过程中的心理学因素,揭示了测试人员的心理动态对测试效率和质量的影响。通过分析测试人员的压力源、动机以及团队间的沟通问题,提出了一系列改善策略,如建立积极的反馈机制、提供持续的职业培训和优化工作环境等,旨在提升软件测试的整体效能。文章最后提出一个开放性问题,邀请读者思考如何在不断变化的技术环境中维持测试团队的心理健康和动力。
|
2月前
|
Java 测试技术
hyengine microbench测试问题之提升jit优化如何解决
hyengine microbench测试问题之提升jit优化如何解决
|
2月前
|
SQL 缓存 关系型数据库
MySQL配置简单优化与读写测试
MySQL配置简单优化与读写测试
|
3月前
|
机器学习/深度学习 算法 搜索推荐
优化IAA广告策略:通过A/B测试和实时反馈提高广告效果
【7月更文第30天】本文将介绍如何使用数据分析技术,特别是A/B测试和实时反馈机制,来改进移动应用内的广告策略。我们将展示一个实际案例,包括如何设置实验、收集数据、分析结果,并根据这些结果调整广告策略以实现更好的用户参与度和收入增长。
158 0
下一篇
无影云桌面