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进行规格选择与性能压测。
目录
相关文章
|
23天前
|
人工智能 前端开发 测试技术
探索软件测试中的自动化框架选择与优化策略####
本文深入剖析了当前主流的自动化测试框架,通过对比分析各自的优势、局限性及适用场景,为读者提供了一套系统性的选择与优化指南。文章首先概述了自动化测试的重要性及其在软件开发生命周期中的位置,接着逐一探讨了Selenium、Appium、Cypress等热门框架的特点,并通过实际案例展示了如何根据项目需求灵活选用与配置框架,以提升测试效率和质量。最后,文章还分享了若干最佳实践和未来趋势预测,旨在帮助测试工程师更好地应对复杂多变的测试环境。 ####
42 4
|
29天前
|
机器学习/深度学习 前端开发 测试技术
探索软件测试中的自动化测试框架选择与优化策略####
本文深入探讨了在当前软件开发生命周期中,自动化测试框架的选择对于提升测试效率、保障产品质量的重要性。通过分析市场上主流的自动化测试工具,如Selenium、Appium、Jest等,结合具体项目需求,提出了一套系统化的选型与优化策略。文章首先概述了自动化测试的基本原理及其在现代软件开发中的角色变迁,随后详细对比了各主流框架的功能特点、适用场景及优缺点,最后基于实际案例,阐述了如何根据项目特性量身定制自动化测试解决方案,并给出了持续集成/持续部署(CI/CD)环境下的最佳实践建议。 --- ####
|
16天前
|
人工智能 监控 测试技术
探索软件测试中的自动化框架选择与优化策略####
【10月更文挑战第21天】 本文深入剖析了软件测试领域面临的挑战,聚焦于自动化测试框架的选择与优化这一核心议题。不同于传统摘要的概述方式,本文将以一个虚拟案例“X项目”为线索,通过该项目从手动测试困境到自动化转型的成功历程,生动展现如何根据项目特性精准匹配自动化工具(如Selenium、Appium等),并结合CI/CD流程进行深度集成与持续优化,最终实现测试效率与质量的双重飞跃。读者将跟随“X项目”团队的视角,直观感受自动化框架选型的策略性思考及实践中的优化技巧,获得可借鉴的实战经验。 ####
28 0
|
2月前
|
Web App开发 前端开发 JavaScript
探索Python科学计算的边界:利用Selenium进行Web应用性能测试与优化
【10月更文挑战第6天】随着互联网技术的发展,Web应用程序已经成为人们日常生活和工作中不可或缺的一部分。这些应用不仅需要提供丰富的功能,还必须具备良好的性能表现以保证用户体验。性能测试是确保Web应用能够快速响应用户请求并处理大量并发访问的关键步骤之一。本文将探讨如何使用Python结合Selenium来进行Web应用的性能测试,并通过实际代码示例展示如何识别瓶颈及优化应用。
130 5
|
2月前
|
缓存 监控 算法
软件测试中的性能瓶颈分析与优化策略
【10月更文挑战第6天】 性能测试是确保软件系统在高负载条件下稳定运行的重要手段。本文将深入探讨性能测试的常见瓶颈,包括硬件资源、网络延迟和代码效率等问题。通过具体案例分析,我们将展示如何识别并解决这些问题,从而提升软件的整体性能。最后,文章还将分享一些实用的性能优化技巧,帮助读者在日常开发和测试中更好地应对性能挑战。
97 3
|
3月前
|
监控 测试技术 持续交付
软件测试中的性能瓶颈分析与优化策略
性能瓶颈,如同潜伏于软件深处的隐形障碍,悄然阻碍着系统的流畅运行。本文旨在揭示这些瓶颈的形成机理,剖析其背后的复杂成因,并汇聚一系列针对性的优化策略,为软件开发者提供一套系统性的解决方案。
57 5
|
2月前
|
运维
【运维基础知识】用dos批处理批量替换文件中的某个字符串(本地单元测试通过,部分功能有待优化,欢迎指正)
该脚本用于将C盘test目录下所有以t开头的txt文件中的字符串“123”批量替换为“abc”。通过创建批处理文件并运行,可实现自动化文本替换,适合初学者学习批处理脚本的基础操作与逻辑控制。
156 56
|
13天前
|
机器学习/深度学习 人工智能 Java
探索软件测试中的自动化框架选择与优化策略####
本文深入探讨了在软件测试领域,面对众多自动化测试框架时,如何根据项目特性、团队技能及长远规划做出最佳选择,并进一步阐述了优化这些框架以提升测试效率与质量的策略。通过对比分析主流自动化测试框架的优劣,结合具体案例,本文旨在为测试团队提供一套实用的框架选型与优化指南。 ####
|
29天前
|
缓存 监控 测试技术
全网最全压测指南!教你如何测试和优化系统极限性能
大家好,我是小米。本文将介绍如何在实际项目中进行性能压测和优化,包括单台服务器和集群压测、使用JMeter、监控CPU和内存使用率、优化Tomcat和数据库配置等方面的内容,帮助你在高并发场景下提升系统性能。希望这些实战经验能助你一臂之力!
55 3
|
2月前
|
缓存 监控 测试技术
软件测试中的性能瓶颈分析与优化策略
本文深入探讨了在软件测试过程中,如何有效地识别和解决性能瓶颈问题。通过对性能瓶颈的定义、分类以及常见原因的分析,结合实际案例,提出了一系列针对性的优化策略和方法。这些策略旨在帮助测试人员和开发人员提高软件的性能表现,确保软件在高负载条件下依然能够稳定运行。