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进行规格选择与性能压测。
目录
相关文章
|
28天前
|
Web App开发 前端开发 JavaScript
探索Python科学计算的边界:利用Selenium进行Web应用性能测试与优化
【10月更文挑战第6天】随着互联网技术的发展,Web应用程序已经成为人们日常生活和工作中不可或缺的一部分。这些应用不仅需要提供丰富的功能,还必须具备良好的性能表现以保证用户体验。性能测试是确保Web应用能够快速响应用户请求并处理大量并发访问的关键步骤之一。本文将探讨如何使用Python结合Selenium来进行Web应用的性能测试,并通过实际代码示例展示如何识别瓶颈及优化应用。
94 5
|
27天前
|
缓存 监控 算法
软件测试中的性能瓶颈分析与优化策略
【10月更文挑战第6天】 性能测试是确保软件系统在高负载条件下稳定运行的重要手段。本文将深入探讨性能测试的常见瓶颈,包括硬件资源、网络延迟和代码效率等问题。通过具体案例分析,我们将展示如何识别并解决这些问题,从而提升软件的整体性能。最后,文章还将分享一些实用的性能优化技巧,帮助读者在日常开发和测试中更好地应对性能挑战。
70 3
|
2月前
|
监控 测试技术 持续交付
软件测试中的性能瓶颈分析与优化策略
性能瓶颈,如同潜伏于软件深处的隐形障碍,悄然阻碍着系统的流畅运行。本文旨在揭示这些瓶颈的形成机理,剖析其背后的复杂成因,并汇聚一系列针对性的优化策略,为软件开发者提供一套系统性的解决方案。
49 5
|
25天前
|
运维
【运维基础知识】用dos批处理批量替换文件中的某个字符串(本地单元测试通过,部分功能有待优化,欢迎指正)
该脚本用于将C盘test目录下所有以t开头的txt文件中的字符串“123”批量替换为“abc”。通过创建批处理文件并运行,可实现自动化文本替换,适合初学者学习批处理脚本的基础操作与逻辑控制。
119 56
|
1月前
|
缓存 监控 测试技术
软件测试中的性能瓶颈分析与优化策略
本文深入探讨了在软件测试过程中,如何有效地识别和解决性能瓶颈问题。通过对性能瓶颈的定义、分类以及常见原因的分析,结合实际案例,提出了一系列针对性的优化策略和方法。这些策略旨在帮助测试人员和开发人员提高软件的性能表现,确保软件在高负载条件下依然能够稳定运行。
|
5月前
|
监控 测试技术 UED
软件测试中的性能瓶颈定位与优化策略
在软件开发的生命周期中,性能测试是确保产品质量的关键步骤之一。本文深入探讨了性能测试的重要性,并提出了一套系统的性能瓶颈定位与优化策略。通过分析现代软件系统中常见的性能问题,结合最新的研究成果和行业最佳实践,文章详细介绍了如何运用科学严谨的方法来识别和解决性能瓶颈。此外,本文还强调了逻辑严密的问题分析框架和数据驱动的决策过程对于提升软件性能的重要性。
|
2月前
|
监控 算法 测试技术
软件测试中的性能瓶颈分析与优化策略
本文旨在深入探讨软件测试过程中性能瓶颈的识别与优化方法。通过对性能瓶颈的概念、分类及其成因进行分析,结合实际案例,提出一套系统的性能瓶颈诊断流程和针对性的优化策略。文章首先概述了性能瓶颈的基本特征,随后详细介绍了内存泄漏、资源竞争、算法效率低下等常见瓶颈类型,并阐述了如何通过代码审查、性能监测工具以及负载测试等手段有效定位问题。最后,结合最佳实践,讨论了代码级优化、系统配置调整、架构改进等多方面的解决措施,旨在为软件开发和测试人员提供实用的性能优化指导。
67 4
|
2月前
|
关系型数据库 MySQL 测试技术
《性能测试》读书笔记_数据库优化
《性能测试》读书笔记_数据库优化
30 7
|
2月前
|
缓存 监控 算法
软件测试中的性能瓶颈定位与优化策略
性能瓶颈,如同隐藏在系统深处的“拦路虎”,悄无声息地制约着软件的表现。本文将揭示如何通过一系列科学方法,识别并消除这些障碍,从而显著提升软件性能,确保用户享受到流畅无阻的数字体验。
|
3月前
|
存储 人工智能 自然语言处理
知识库优化增强,支持多种数据类型、多种检索策略、召回测试 | Botnow上新
Botnow近期对其知识库功能进行了全面升级,显著提升了数据处理能力、检索效率及准确性。新版本支持多样化的数据格式,包括PDF、Word、TXT、Excel和CSV等文件,无需额外转换即可直接导入,极大地丰富了知识来源。此外,还新增了细致的文本分片管理和编辑功能,以及表格数据的结构化处理,使知识管理更为精细化。 同时,平台提供了多种检索策略,包括混合检索、语义检索和全文检索等,可根据具体需求灵活选择,有效解决了大模型幻觉问题,增强了专业领域的知识覆盖,从而显著提高了回复的准确性。这些改进广泛适用于客服咨询、知识问答等多种应用场景,极大提升了用户体验和交互质量。
72 4