成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

java高性能反射及性能對(duì)比

HtmlCssJs / 1028人閱讀

摘要:介紹是使用字節(jié)碼生成來(lái)加強(qiáng)反射的性能。實(shí)現(xiàn)原理方法字節(jié)碼生成大致邏輯為通過(guò)反射獲取必要的函數(shù)名函數(shù)類型等信息。由于里面包含字節(jié)碼生成操作,所以相對(duì)來(lái)說(shuō)這個(gè)函數(shù)是比較耗時(shí)的。

java編程中,使用反射來(lái)增強(qiáng)靈活性(如各類框架)、某些抽象(如各類框架)及減少樣板代碼(如Java Bean)。
因此,反射在實(shí)際的java項(xiàng)目中被大量使用。

由于項(xiàng)目里存在反射的性能瓶頸,使用的是ReflectASM高性能反射庫(kù)來(lái)優(yōu)化。
因此,在空閑時(shí)間研究了下的這個(gè)庫(kù),并做了簡(jiǎn)單的Beachmark。

介紹

ReflectASM是使用字節(jié)碼生成來(lái)加強(qiáng)反射的性能。
反射包含多種反射,這個(gè)庫(kù)很簡(jiǎn)單,它提供的特性則是:

根據(jù)匹配的字符串操作成員變量。

根據(jù)匹配的字符串調(diào)用成員函數(shù)。

根據(jù)匹配的字符串調(diào)用構(gòu)造函數(shù)。

這三種也恰恰是實(shí)際使用中最多的,且在特殊場(chǎng)景下也容易產(chǎn)生性能問(wèn)題。

例子

舉個(gè)例子,使用MethodAccess來(lái)反射調(diào)用類的函數(shù):

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

更多的例子參考官方文檔,這個(gè)庫(kù)本身就不大,就幾個(gè)類。

實(shí)現(xiàn)原理 MethodAccess.get方法
static public MethodAccess get (Class type) {
    ArrayList methods = new ArrayList();
    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;
            /* ... 字節(jié)碼生成 */
            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);
    }
}

大致邏輯為:

通過(guò)java反射獲取必要的函數(shù)名、函數(shù)類型等信息。

動(dòng)態(tài)生成一個(gè)用于調(diào)用被反射對(duì)象的類,其為MethodAccess的子類。

反射生成動(dòng)態(tài)生成的類,返回。

由于里面包含字節(jié)碼生成操作,所以相對(duì)來(lái)說(shuō)這個(gè)函數(shù)是比較耗時(shí)的。
我們來(lái)分析一下,如果第二次調(diào)用對(duì)相同的類調(diào)用MethodAccess.get()方法,會(huì)不會(huì)好一些?
注意到:

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

因此,如果這個(gè)動(dòng)態(tài)生成的MethodAccess類已經(jīng)生成過(guò),第二次調(diào)用MethodAccess.get是不會(huì)操作字節(jié)碼生成的。
但是,前面的一大堆準(zhǔn)備反射信息的操作依然會(huì)被執(zhí)行。所以,如果在代碼中封裝這樣的一個(gè)函數(shù)試圖使用ReflectASM庫(kù):

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

那么每次反射調(diào)用前都得執(zhí)行這么一大坨準(zhǔn)備反射信息的代碼,實(shí)際上還不如用原生反射呢。這個(gè)后面會(huì)有Beachmark。

為什么不在找不到動(dòng)態(tài)生成的MethodAccess類時(shí)(即第一次調(diào)用)時(shí),再準(zhǔn)備反射信息?這個(gè)得問(wèn)作者。

動(dòng)態(tài)生成的類 通過(guò)idea調(diào)試器獲取動(dòng)態(tài)生成類的字節(jié)碼

那么那個(gè)動(dòng)態(tài)生成的類的內(nèi)部到底是什么?
由于這個(gè)類是動(dòng)態(tài)生成的,所以獲取它的定義比較麻煩。
一開始我試圖尋找java的ClassLoader的API獲取它的字節(jié)碼,但是似乎沒(méi)有這種API。

后來(lái),我想了一個(gè)辦法,直接在MethodAccess.get里面的這行代碼打斷點(diǎn):

byte[] data = cw.toByteArray();

通過(guò)idea的調(diào)試器把data的內(nèi)容復(fù)制出來(lái)。但是這又遇到一個(gè)問(wèn)題,data是二進(jìn)制內(nèi)容,根本復(fù)制不出來(lái)。
一個(gè)一年要400美刀的IDE,為啥不能做的貼心一點(diǎn)???

既然是二進(jìn)制內(nèi)容,那么只能設(shè)法將其編碼成文本再?gòu)?fù)制了。通過(guò)idea調(diào)試器自定義view的功能,將其編碼成base64后復(fù)制了出來(lái)。
然后,搞個(gè)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方法中,直接根據(jù)索引使用switch直接調(diào)用。
所以,只要使用得當(dāng),性能媲美原生調(diào)用是沒(méi)有什么問(wèn)題的。

MethodAccess.invoke方法

來(lái)看invoke方法內(nèi)具體做了哪些操作:

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

    /** Invokes the method with the specified name and the specified param types. */
    public Object invoke (Object object, String methodName, Class[] paramTypes, Object... args) {
        return invoke(object, getIndex(methodName, paramTypes), args);
    }

    /** Invokes the first method with the specified name and the specified number of arguments. */
    public Object invoke (Object object, String methodName, Object... args) {
        return invoke(object, getIndex(methodName, args == null ? 0 : args.length), args);
    }

    /** Returns the index of the first method with the specified name. */
    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);
    }

如果通過(guò)函數(shù)名稱調(diào)用函數(shù)(即調(diào)用invoke(Object, String, Class[], Object...)
MethodAccess是先遍歷所有函數(shù)名稱拿到索引,然后根據(jù)索引調(diào)用對(duì)應(yīng)方法(即調(diào)用虛函數(shù)invoke(Object, int, Object...)
實(shí)際上是通過(guò)多態(tài)調(diào)用字節(jié)碼動(dòng)態(tài)生成的子類的對(duì)應(yīng)函數(shù)。

如果被反射調(diào)用的類的函數(shù)很多,則這個(gè)遍歷操作帶來(lái)的性能損失不能忽略。
所以,性能要求高的場(chǎng)合,應(yīng)該預(yù)先通過(guò)getIndex方法提前獲得索引,然后后面即可以直接使用invoke(Object, int, Object...)來(lái)調(diào)用。

Beachmark

談這種細(xì)粒度操作級(jí)別的性能問(wèn)題,最有說(shuō)服力的就是實(shí)際測(cè)試數(shù)據(jù)了。
下面,Talk is cheap, show you my beachmark.

首先是相關(guān)環(huán)境:
操作系統(tǒng)版本: elementary OS 0.4.1 Loki 64-bit
CPU: 雙核 Intel? Core? i5-7200U CPU @ 2.50GHz
JMH基準(zhǔn)測(cè)試框架版本: 1.21
JVM版本: JDK 1.8.0_181, OpenJDK 64-Bit Server VM, 25.181-b13

Benchmark                                                Mode  Cnt     Score    Error   Units
// 通過(guò)MethodHandle調(diào)用。預(yù)先得到某函數(shù)的MethodHandle
ReflectASMBenchmark.javaMethodHandleWithInitGet         thrpt    5   122.988 ±  4.240  ops/us
// 通過(guò)java反射調(diào)用。緩存得到的Method對(duì)象
ReflectASMBenchmark.javaReflectWithCacheGet             thrpt    5    11.877 ±  2.203  ops/us
// 通過(guò)java反射調(diào)用。預(yù)先得到某函數(shù)的Method對(duì)象
ReflectASMBenchmark.javaReflectWithInitGet              thrpt    5    66.702 ± 11.154  ops/us
// 通過(guò)java反射調(diào)用。每次調(diào)用都先取得Method對(duì)象
ReflectASMBenchmark.javaReflectWithOriginGet            thrpt    5     3.654 ±  0.795  ops/us
// 直接調(diào)用
ReflectASMBenchmark.normalCall                          thrpt    5  1059.926 ± 99.724  ops/us
// ReflectASM通過(guò)索引調(diào)用。預(yù)先取得MethodAccess對(duì)象,預(yù)先取得某函數(shù)的索引
ReflectASMBenchmark.reflectAsmIndexWithCacheGet         thrpt    5   639.051 ± 47.750  ops/us
// ReflectASM通過(guò)函數(shù)名調(diào)用,緩存得到的MethodAccess對(duì)象
ReflectASMBenchmark.reflectAsmWithCacheGet              thrpt    5    21.868 ±  1.879  ops/us
// ReflectASM通過(guò)函數(shù)名調(diào)用,預(yù)先得到的MethodAccess
ReflectASMBenchmark.reflectAsmWithInitGet               thrpt    5    53.370 ±  0.821  ops/us
// ReflectASM通過(guò)函數(shù)名調(diào)用,每次調(diào)用都取得MethodAccess
ReflectASMBenchmark.reflectAsmWithOriginGet             thrpt    5     0.593 ±  0.005  ops/us

可以看到,每次調(diào)用都來(lái)一次MethodAccess.get,性能是最慢的,時(shí)間消耗是java原生調(diào)用的6倍,不如用java原生調(diào)用。
最快的則是預(yù)先取得MethodAccess和函數(shù)的索引并用索引來(lái)調(diào)用。其時(shí)間消耗僅僅是直接調(diào)用的2倍不到。

基準(zhǔn)測(cè)試代碼見:
https://github.com/frapples/j...

jmh框架十分專業(yè),在基準(zhǔn)測(cè)試前會(huì)做復(fù)雜的預(yù)熱過(guò)程以減少環(huán)境、優(yōu)化等影響,基準(zhǔn)測(cè)試也盡可能通過(guò)合理的迭代次數(shù)等方式來(lái)減小誤差。
所以,在默認(rèn)的迭代次數(shù)、預(yù)熱次數(shù)下,跑一次基準(zhǔn)測(cè)試的時(shí)間不短,CPU呼呼的轉(zhuǎn)。。。

最后總結(jié)

在使用ReflectASM對(duì)某類進(jìn)行反射調(diào)用時(shí),需要預(yù)先生成或獲取字節(jié)碼動(dòng)態(tài)生成的MethodAccess子類對(duì)象。

這一操作是非常耗時(shí)的,所以正確的使用方法應(yīng)該是:

在某個(gè)利用反射的耗時(shí)函數(shù)啟動(dòng)前,先預(yù)先生成這個(gè)MethodAccess對(duì)象。

如果是自己里面ReflectASM封裝工具類,則應(yīng)該設(shè)計(jì)緩存,緩存生成的MethodAccess對(duì)象。

如果不這樣做,這個(gè)ReflectASM用的沒(méi)有任何意義,性能還不如java的原生反射。

如果想進(jìn)一步提升性能,那么還應(yīng)該避免使用函數(shù)的字符串名稱來(lái)調(diào)用,而是在耗時(shí)的函數(shù)啟動(dòng)前,預(yù)先獲取函數(shù)名稱對(duì)應(yīng)的整數(shù)索引。
在后面的耗時(shí)的函數(shù),使用這個(gè)整數(shù)索引進(jìn)行調(diào)用。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/72125.html

相關(guān)文章

  • Java學(xué)習(xí)路線總結(jié),搬磚工逆襲Java架構(gòu)師(全網(wǎng)最強(qiáng))

    摘要:哪吒社區(qū)技能樹打卡打卡貼函數(shù)式接口簡(jiǎn)介領(lǐng)域優(yōu)質(zhì)創(chuàng)作者哪吒公眾號(hào)作者架構(gòu)師奮斗者掃描主頁(yè)左側(cè)二維碼,加入群聊,一起學(xué)習(xí)一起進(jìn)步歡迎點(diǎn)贊收藏留言前情提要無(wú)意間聽到領(lǐng)導(dǎo)們的談話,現(xiàn)在公司的現(xiàn)狀是碼農(nóng)太多,但能獨(dú)立帶隊(duì)的人太少,簡(jiǎn)而言之,不缺干 ? 哪吒社區(qū)Java技能樹打卡?【打卡貼 day2...

    Scorpion 評(píng)論0 收藏0
  • 你和阿里資深架構(gòu)師之間,差的不僅僅是年齡(進(jìn)階必看)

    摘要:導(dǎo)讀閱讀本文需要有足夠的時(shí)間,筆者會(huì)由淺到深帶你一步一步了解一個(gè)資深架構(gòu)師所要掌握的各類知識(shí)點(diǎn),你也可以按照文章中所列的知識(shí)體系對(duì)比自身,對(duì)自己進(jìn)行查漏補(bǔ)缺,覺得本文對(duì)你有幫助的話,可以點(diǎn)贊關(guān)注一下。目錄一基礎(chǔ)篇二進(jìn)階篇三高級(jí)篇四架構(gòu)篇五擴(kuò) 導(dǎo)讀:閱讀本文需要有足夠的時(shí)間,筆者會(huì)由淺到深帶你一步一步了解一個(gè)資深架構(gòu)師所要掌握的各類知識(shí)點(diǎn),你也可以按照文章中所列的知識(shí)體系對(duì)比自身,對(duì)自己...

    huaixiaoz 評(píng)論0 收藏0
  • Java面試通關(guān)要點(diǎn)匯總集(基礎(chǔ)篇之基本功,非原作者)

    摘要:面試通關(guān)要點(diǎn)匯總集部分解答說(shuō)明如果你有幸能看到的話,本文整體框架來(lái)自阿里梁桂釗的博文,總結(jié)的非常不錯(cuò)。這樣做的目的是對(duì)內(nèi)部數(shù)據(jù)進(jìn)行了不同級(jí)別的保護(hù),防止錯(cuò)誤的使用了對(duì)象的私有部分。被繼承的類稱為基類和父類或超類。 showImg(https://segmentfault.com/img/remote/1460000013442471?w=1280&h=819); Java面試通關(guān)要點(diǎn)匯...

    smallStone 評(píng)論0 收藏0
  • Java知識(shí)點(diǎn)總結(jié)(反射-反射機(jī)制性能問(wèn)題)

    摘要:知識(shí)點(diǎn)總結(jié)反射反射機(jī)制性能問(wèn)題知識(shí)點(diǎn)總結(jié)反射性能相關(guān)注意點(diǎn)啟用和禁用訪問(wèn)安全檢查的開關(guān)值為則指示反射的對(duì)象在使用時(shí)應(yīng)該取消語(yǔ)言訪問(wèn)檢查。并不是為就能訪問(wèn)為就不能訪問(wèn)。禁止安全檢查,可以提高反射的運(yùn)行速度。 Java知識(shí)點(diǎn)總結(jié)(反射-反射機(jī)制性能問(wèn)題) @(Java知識(shí)點(diǎn)總結(jié))[Java, 反射] 性能相關(guān)注意點(diǎn): setAccessible 啟用和禁用訪問(wèn)安全檢查的開關(guān),值為 tru...

    LeexMuller 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<