iis服务器助手广告
返回顶部
首页 > 资讯 > 后端开发 > Python >java高性能反射及性能对比
  • 951
分享到

java高性能反射及性能对比

高性能反射性能 2023-01-31 07:01:57 951人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

java编程中,使用反射来增强灵活性(如各类框架)、某些抽象(如各类框架)及减少样板代码(如Java Bean)。因此,反射在实际的java项目中被大量使用。 由于项目里存在反射的性能瓶颈,使用的是ReflectASM高性能反射库来优化。

java编程中,使用反射来增强灵活性(如各类框架)、某些抽象(如各类框架)及减少样板代码(如Java Bean)。
因此,反射在实际的java项目中被大量使用。

由于项目里存在反射的性能瓶颈,使用的是ReflectASM高性能反射库来优化
因此,在空闲时间研究了下的这个库,并做了简单的Beachmark。

<!--more-->

ReflectASM是使用字节码生成来加强反射的性能。
反射包含多种反射,这个库很简单,它提供的特性则是:

  1. 根据匹配的字符串操作成员变量。
  2. 根据匹配的字符串调用成员函数。
  3. 根据匹配的字符串调用构造函数。

这三种也恰恰是实际使用中最多的,且在特殊场景下也容易产生性能问题。

举个例子,使用MethodAccess来反射调用类的函数:

Person person = new Person();
MethodAccess m = MethodAccess.get(Person.class);
Object value = m.invoke(person, "getName");

更多的例子参考官方文档,这个库本身就不大,就几个类。

MethodAccess.get方法

static public MethodAccess get (Class type) {
    ArrayList<Method> methods = new ArrayList<Method>();
    boolean isInterface = type.isInterface();
    if (!isInterface) {
        Class nextClass = type;
        while (nextClass != Object.class) {
            aDDDeclaredMethodsToList(nextClass, methods);
            nextClass = nextClass.getSuperclass();
        }
    } else {
        recursiveAddInterfaceMethodsToList(type, methods);
    }

    int n = methods.size();
    String[] methodNames = new String[n];
    Class[][] parameterTypes = new Class[n][];
    Class[] returnTypes = new Class[n];
    for (int i = 0; i < n; i++) {
        Method method = methods.get(i);
        methodNames[i] = method.getName();
        parameterTypes[i] = method.getParameterTypes();
        returnTypes[i] = method.getReturnType();
    }

    String className = type.getName();
    String accessClassName = className + "MethodAccess";
    if (accessClassName.startsWith("java.")) accessClassName = "reflectasm." + accessClassName;
    Class accessClass;

    AccessClassLoader loader = AccessClassLoader.get(type);
    synchronized (loader) {
        try {
            accessClass = loader.loadClass(accessClassName);
        } catch (ClassNotFoundException ignored) {
            String accessClassNameInternal = accessClassName.replace('.', '/');
            String classNameInternal = className.replace('.', '/');

            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
            MethodVisitor mv;
            
            byte[] data = cw.toByteArray();
            accessClass = loader.defineClass(accessClassName, data);
        }                
    }

    try {
        MethodAccess access = (MethodAccess)accessClass.newInstance();
        access.methodNames = methodNames;
        access.parameterTypes = parameterTypes;
        access.returnTypes = returnTypes;
        return access;
    } catch (Throwable t) {
        throw new RuntimeException("Error constructing method access class: " + accessClassName, t);
    }
}

大致逻辑为:

  1. 通过java反射获取必要的函数名、函数类型等信息。
  2. 动态生成一个用于调用被反射对象的类,其为MethodAccess的子类。
  3. 反射生成动态生成的类,返回。

由于里面包含字节码生成操作,所以相对来说这个函数是比较耗时的。
我们来分析一下,如果第二次调用对相同的类调用MethodAccess.get()方法,会不会好一些?
注意到:

synchronized (loader) {
    try {
        accessClass = loader.loadClass(accessClassName);
    } catch {
        
    }
}

因此,如果这个动态生成的MethodAccess类已经生成过,第二次调用MethodAccess.get是不会操作字节码生成的。
但是,前面的一大堆准备反射信息的操作依然会被执行。所以,如果在代码中封装这样的一个函数试图使用ReflectASM库:

Object reflectionInvoke(Object bean, String methodName) {
    MethodAccess m = MethodAccess.get(bean.getClass());
    return m.invoke(bean, methodName);
}

那么每次反射调用前都得执行这么一大坨准备反射信息的代码,实际上还不如用原生反射呢。这个后面会有Beachmark。

为什么不在找不到动态生成的MethodAccess类时(即第一次调用)时,再准备反射信息?这个得问作者。

动态生成的类

通过idea调试器获取动态生成类的字节码

那么那个动态生成的类的内部到底是什么?
由于这个类是动态生成的,所以获取它的定义比较麻烦。
一开始我试图寻找java的ClassLoader的api获取它的字节码,但是似乎没有这种API。

后来,我想了一个办法,直接在MethodAccess.get里面的这行代码打断点:

byte[] data = cw.toByteArray();

通过idea的调试器把data的内容复制出来。但是这又遇到一个问题,data是二进制内容,根本复制不出来。
一个一年要400美刀的IDE,为啥不能做的贴心一点啊?

既然是二进制内容,那么只能设法将其编码成文本再复制了。通过idea调试器自定义view的功能,将其编码成base64后复制了出来。
然后,搞个python小脚本将其base64解码回.class文件:

#!/usr/bin/env python3
import base64

with open("tmp.txt", "rb") as fi, open("tmp.class", "wb") as fo:
    base64.decode(fi, fo)

反编译.class文件,得到:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package io.GitHub.frapples.javademoandcookbook.commonutils.entity;

import com.esotericsoftware.reflectasm.MethodAccess;

public class PointMethodAccess extends MethodAccess {
    public PointMethodAccess() {
    }

    public Object invoke(Object var1, int var2, Object... var3) {
        Point var4 = (Point)var1;
        switch(var2) {
        case 0:
            return var4.getX();
        case 1:
            var4.setX((Integer)var3[0]);
            return null;
        case 2:
            return var4.getY();
        case 3:
            var4.setY((Integer)var3[0]);
            return null;
        case 4:
            return var4.toString();
        case 5:
            return Point.of((Integer)var3[0], (Integer)var3[1], (String)var3[2]);
        default:
            throw new IllegalArgumentException("Method not found: " + var2);
        }
    }
}

可以看到,生成的invoke方法中,直接根据索引使用switch直接调用。
所以,只要使用得当,性能媲美原生调用是没有什么问题的。

MethodAccess.invoke方法

来看invoke方法内具体做了哪些操作:

    abstract public Object invoke (Object object, int methodIndex, Object... args);

    
    public Object invoke (Object object, String methodName, Class[] paramTypes, Object... args) {
        return invoke(object, getIndex(methodName, paramTypes), args);
    }

    
    public Object invoke (Object object, String methodName, Object... args) {
        return invoke(object, getIndex(methodName, args == null ? 0 : args.length), args);
    }

    
    public int getIndex (String methodName) {
        for (int i = 0, n = methodNames.length; i < n; i++)
            if (methodNames[i].equals(methodName)) return i;
        throw new IllegalArgumentException("Unable to find non-private method: " + methodName);
    }

如果通过函数名称调用函数(即调用invoke(Object, String, Class[], Object...)
MethodAccess是先遍历所有函数名称拿到索引,然后根据索引调用对应方法(即调用虚函数invoke(Object, int, Object...)
实际上是通过多态调用字节码动态生成的子类的对应函数。

如果被反射调用的类的函数很多,则这个遍历操作带来的性能损失不能忽略。
所以,性能要求高的场合,应该预先通过getIndex方法提前获得索引,然后后面即可以直接使用invoke(Object, int, Object...)来调用。

谈这种细粒度操作级别的性能问题,最有说服力的就是实际测试数据了。
下面,Talk is cheap, show you my beachmark.

首先是相关环境:
操作系统版本: elementary OS 0.4.1 Loki 64-bit
CPU: 双核 Intel® Core™ i5-7200U CPU @ 2.50GHz
JMH基准测试框架版本: 1.21
JVM版本: jdk 1.8.0_181, OpenJDK 64-Bit Server VM, 25.181-b13

Benchmark                                                Mode  Cnt     Score    Error   Units
// 通过MethodHandle调用。预先得到某函数的MethodHandle
ReflectASMBenchmark.javaMethodHandleWithInitGet         thrpt    5   122.988 ±  4.240  ops/us
// 通过java反射调用。缓存得到的Method对象
ReflectASMBenchmark.javaReflectWithCacheGet             thrpt    5    11.877 ±  2.203  ops/us
// 通过java反射调用。预先得到某函数的Method对象
ReflectASMBenchmark.javaReflectWithInitGet              thrpt    5    66.702 ± 11.154  ops/us
// 通过java反射调用。每次调用都先取得Method对象
ReflectASMBenchmark.javaReflectWithOriginGet            thrpt    5     3.654 ±  0.795  ops/us
// 直接调用
ReflectASMBenchmark.nORMalCall                          thrpt    5  1059.926 ± 99.724  ops/us
// ReflectASM通过索引调用。预先取得MethodAccess对象,预先取得某函数的索引
ReflectASMBenchmark.reflectAsmIndexWithCacheGet         thrpt    5   639.051 ± 47.750  ops/us
// ReflectASM通过函数名调用,缓存得到的MethodAccess对象
ReflectASMBenchmark.reflectAsmWithCacheGet              thrpt    5    21.868 ±  1.879  ops/us
// ReflectASM通过函数名调用,预先得到的MethodAccess
ReflectASMBenchmark.reflectAsmWithInitGet               thrpt    5    53.370 ±  0.821  ops/us
// ReflectASM通过函数名调用,每次调用都取得MethodAccess
ReflectASMBenchmark.reflectAsmWithOriginGet             thrpt    5     0.593 ±  0.005  ops/us

可以看到,每次调用都来一次MethodAccess.get,性能是最慢的,时间消耗是java原生调用的6倍,不如用java原生调用。
最快的则是预先取得MethodAccess和函数的索引并用索引来调用。其时间消耗仅仅是直接调用的2倍不到。

基准测试代码见:
https://github.com/frapples/j...

jmh框架十分专业,在基准测试前会做复杂的预热过程以减少环境、优化等影响,基准测试也尽可能通过合理的迭代次数等方式来减小误差。
所以,在默认的迭代次数、预热次数下,跑一次基准测试的时间不短,CPU呼呼的转。。。

在使用ReflectASM对某类进行反射调用时,需要预先生成或获取字节码动态生成的MethodAccess子类对象。

这一操作是非常耗时的,所以正确的使用方法应该是:

  1. 在某个利用反射的耗时函数启动前,先预先生成这个MethodAccess对象。
  2. 如果是自己里面ReflectASM封装工具类,则应该设计缓存,缓存生成的MethodAccess对象。

如果不这样做,这个ReflectASM用的没有任何意义,性能还不如java的原生反射。

如果想进一步提升性能,那么还应该避免使用函数的字符串名称来调用,而是在耗时的函数启动前,预先获取函数名称对应的整数索引。
在后面的耗时的函数,使用这个整数索引进行调用。

--结束END--

本文标题: java高性能反射及性能对比

本文链接: http://www.lsjlt.com/news/192244.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • java高性能反射及性能对比
    java编程中,使用反射来增强灵活性(如各类框架)、某些抽象(如各类框架)及减少样板代码(如Java Bean)。因此,反射在实际的java项目中被大量使用。 由于项目里存在反射的性能瓶颈,使用的是ReflectASM高性能反射库来优化。...
    99+
    2023-01-31
    高性能 反射 性能
  • Java反射及性能详细
    目录一、准备二、反射调用流程1.反射的使用2.getMethod 和 getDeclaredMethod区别三、调用反射方法四、反射效率低的原因五、反射优化我们今天不探讨框架层面的内...
    99+
    2024-04-02
  • GO反射对性能的影响分析
    目录写在前面代码性能分析写在后面写在前面 今天在公司写了一段代码,判断一个变量是否为空值,由于判断的类型太少,code review的时候同事说还有很多类型没有考虑到,并且提到有没...
    99+
    2023-01-06
    GO 反射性能分析 GO 反射
  • defineProperty和Proxy基础功能及性能对比
    目录前言Object.defineProperty简介语法简单示例仿vue使用对象的拦截数组的拦截Proxy简介语法简单示例拦截的本质孪生兄弟Reflect仿vue使用对象的拦截数组...
    99+
    2022-11-13
    efineProperty Proxy基础 efineProperty对比Proxy性能
  • Java的反射是怎么影响性能的
    本篇内容介绍了“Java的反射是怎么影响性能的”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!反射具体是怎么影响性能的?这引起了我的反思。是啊...
    99+
    2023-06-15
  • java数组中性能对比实例
    这篇文章给大家分享的是有关java数组中性能对比实例的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。java基本数据类型有哪些Java的基本数据类型分为:1、整数类型,用来表示整数的数据类型。2、浮点类型,用来表示...
    99+
    2023-06-14
  • java中Memcached和Redis的性能对比
    这篇文章将为大家详细讲解有关java中Memcached和Redis的性能对比,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。Java的特点有哪些Java的特点有哪些1.Java语言作为静态面...
    99+
    2023-06-14
  • PHP数组反转性能比较
    在 php 中,反转数组的方法性能从快到慢依次为:array_reverse() 函数、手动反转使用 for 循环、手动反转使用 while 循环。在测试数组大小为 10,00...
    99+
    2024-04-28
    php 数组
  • MessagePack和System.Text.Json序列化和反序列化性能及对比分析
    本博客将测试MessagePack 和System.Text.Json 序列化和反序列化性能项目文件: Program.cs代码: using BenchmarkDotNet.Ru...
    99+
    2023-01-28
    MessagePack 和System.Text.Json 序列化和反序列化 MessagePack 和System.Text.Json 序列化和反序列化
  • 浅谈Java模板引擎性能对比
    从Github上翻到对JSP、Thymeleaf 3、Velocity 1.7、Freemarker 2.3.23几款主流模板的性能对比,总体上看,Freemarker、Velocity、JSP在性能上差别不大,而Thymeleaf与前三者...
    99+
    2023-05-31
    java 模板引擎 能对
  • NumPy和JavaScript在Java中的性能对比?
    在编程领域,性能一直是一个非常重要的话题。对于数据密集型的应用程序而言,选择适当的工具和技术可以大大提高程序的性能。在这篇文章中,我们将重点比较NumPy和JavaScript在Java中的性能,并为您提供一些示例代码来帮助您更好地理解这...
    99+
    2023-10-18
    load javascript numpy
  • Go库bytes.Buffer和strings.Builder使用及性能对比
    目录前言bytes.Buffer 和 strings.Builder用法区别性能对比前言 字符串拼接是老生常谈了。在 Go 语言中,常见的拼接字符串的方法有:用+号,或者使用fmt...
    99+
    2022-12-15
    Go bytes.Buffer对比strings.Builder bytes.Buffer strings.Builder性能对比
  • 关于dubbo的RPC和RESTful性能及对比
    目录先上结论原因分析HTTP请求代码RPC代码总结先上结论 RPC请求的效率是HTTP请求的1.6倍左右,性能明显比HTTP请求要高很多。 原因分析 RESTful是基于HTTP协议...
    99+
    2022-12-19
    dubbo的RPC dubbo的RESTful RPC和RESTful性能对比
  • PHP 性能优化:基准测试与性能对比
    php 应用程序性能优化至关重要,通过基准测试和性能对比可识别优化领域。基准测试有助于衡量应用程序性能,可用工具包括 apachebench 和 jmeter。性能对比将优化后的应用程序...
    99+
    2024-05-10
    php 性能优化 apache 并发请求
  • Hadoop和spark的性能对比
    本篇内容主要讲解“Hadoop和spark的性能对比”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Hadoop和spark的性能对比”吧!Hadoop和spark的性能有何区别。  如果说Had...
    99+
    2023-06-02
  • 云服务器性能对比
    云服务器是一种虚拟化的云平台,用于托管和管理大量的计算资源和存储资源。因此,云服务器的性能是一项重要的性能指标。 下面是一个简单的PaaS云服务器性能对比分析的示例。假设您的应用程序在一个公有云上运行,您想比较两个PaaS云服务器的性能,...
    99+
    2023-10-26
    性能 服务器
  • PHP高级特性:了解反射机制的强大功能
    非常抱歉,由于您没有提供文章标题,我无法为您生成一篇高质量的文章。请您提供文章标题,我将尽快为您生成一篇优质的文章。...
    99+
    2024-05-15
  • java使用反射给对象属性赋值
    📢 📢 📢 📣 📣 📣 哈喽!大家好,我是「奇点」,江湖人称 singularity。刚工作几年,想和大家一同进步...
    99+
    2023-09-02
    java 反射 Powered by 金山文档
  • Go语言和Java的区别:性能对比
    性能对比:Go语言和Java 概述 Go语言和Java都是流行的编程语言,但它们在性能方面存在一些差异。Go语言因其出色的并发性和低延迟而闻名,而Java则因其稳定性和跨平台性而受到欢迎。在本文中,我们将比较...
    99+
    2024-02-01
    java 区别 go语言
  • Java 反射设置/获取对象属性值
    ✨大家好,我是【zhuzicc】~ ,一位主攻【Java】的 攻城狮!✨ 欢迎对【Java】感兴趣的大佬,关注我 😜 ———————————————— ❤️ ❤️ ❤️ 如果觉得...
    99+
    2023-10-20
    java jvm servlet
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作