java 反射 速度_Java反射,但速度更快
java 反射 速度
在編譯時(shí)不知道Java類的最快方法是什么? Java框架通常會(huì)這樣做。 很多。 它可以直接影響其性能。 因此,讓我們對(duì)不同的方法進(jìn)行基準(zhǔn)測(cè)試,例如反射,方法句柄和代碼生成。
用例
假設(shè)我們有一個(gè)簡(jiǎn)單的Person類,其中包含名稱和地址:
public class Person {...public String getName() {...}public Address getAddress() {...}}并且我們想使用諸如以下的框架:
- XStream ,JAXB或Jackson來(lái)將實(shí)例序列化為XML或JSON。
- JPA /Hibernate將人員存儲(chǔ)在數(shù)據(jù)庫(kù)中。
- OptaPlanner分配地址(如果他們是游客或無(wú)家可歸的人)。
這些框架都不了解Person類。 因此,他們不能簡(jiǎn)單地調(diào)用person.getName() :
// Framework codepublic Object executeGetter(Object object) {// Compilation error: class Person is unknown to the frameworkreturn ((Person) object).getName();}相反,代碼使用反射,方法句柄或代碼生成。
但是這樣的代碼被稱為很多 :
- 如果在數(shù)據(jù)庫(kù)中插入1000個(gè)不同的人員,則JPA / Hibernate可能會(huì)調(diào)用2000次這樣的代碼:
- 1000次調(diào)用Person.getName()
- 同樣,如果您用XML或JSON編寫1000個(gè)不同的人,則XStream,JAXB或Jackson可能會(huì)進(jìn)行2000次調(diào)用。
顯然,當(dāng)這種代碼每秒被調(diào)用x次時(shí), 其性能很重要 。
基準(zhǔn)測(cè)試
使用JMH,我在帶有32GB RAM的64位8核Intel i7-4790臺(tái)式機(jī)上的Linux上使用OpenJDK 1.8.0_111運(yùn)行了一組微型基準(zhǔn)測(cè)試。 JMH基準(zhǔn)測(cè)試有3個(gè)分支,1秒的5個(gè)預(yù)熱迭代和1秒的20個(gè)測(cè)量迭代。
該基準(zhǔn)測(cè)試的源代碼位于此GitHub存儲(chǔ)庫(kù)中 。
TL; DR結(jié)果
- Java反射很慢。 (*)
- Java MethodHandles也很慢。 (*)
- 用javax.tools生成的代碼很快。 (*)
(*)在用例中,我以使用的工作量作為基準(zhǔn)。 你的旅費(fèi)可能會(huì)改變。
因此,魔鬼在細(xì)節(jié)中。 讓我們?yōu)g覽一下實(shí)現(xiàn),以確認(rèn)我應(yīng)用了典型的魔術(shù)技巧(例如setAccessible(true) )。
實(shí)作
直接訪問(wèn)(基準(zhǔn))
我使用了一個(gè)普通的person.getName()調(diào)用作為基準(zhǔn):
public final class MyAccessor {public Object executeGetter(Object object) {return ((Person) object).getName();}}每次操作大約需要2.7納秒:
Benchmark Mode Cnt Score Error Units =================================================== DirectAccess avgt 60 2.667 ± 0.028 ns/op直接訪問(wèn)自然是運(yùn)行時(shí)最快的方法,而沒(méi)有引導(dǎo)成本。 但是它在編譯時(shí)導(dǎo)入Person ,因此每個(gè)框架都無(wú)法使用它。
反射
框架在運(yùn)行時(shí)讀取吸氣劑而無(wú)需事先知道的明顯方法是通過(guò)Java Reflection:
public final class MyAccessor {private final Method getterMethod;public MyAccessor() {getterMethod = Person.class.getMethod("getName");// Skip Java language access checking during executeGetter()getterMethod.setAccessible(true);}public Object executeGetter(Object bean) {return getterMethod.invoke(bean);}}添加setAccessible(true)調(diào)用可以使這些反射調(diào)用更快,但是即使這樣,每個(gè)調(diào)用也要花費(fèi)5.5納秒。
Benchmark Mode Cnt Score Error Units =================================================== DirectAccess avgt 60 2.667 ± 0.028 ns/op Reflection avgt 60 5.511 ± 0.081 ns/op反射比直接訪問(wèn)慢106%(大約慢一倍)。 預(yù)熱還需要更長(zhǎng)的時(shí)間。
這對(duì)我來(lái)說(shuō)不是什么大驚喜,因?yàn)楫?dāng)我使用OptaPlanner在980個(gè)城市中描述(使用抽樣)一個(gè)人為簡(jiǎn)單的旅行推銷員問(wèn)題時(shí),反射成本像拇指酸痛一樣突出:
方法句柄
Java 7中引入了MethodHandle以支持invokedynamic指令。 根據(jù)javadoc,它是對(duì)基礎(chǔ)方法的類型化,直接可執(zhí)行的引用。 聽(tīng)起來(lái)快吧?
public final class MyAccessor {private final MethodHandle getterMethodHandle;public MyAccessor() {MethodHandle temp = lookup.findVirtual(Person.class, "getName", MethodType.methodType(String.class));temp = temp.asType(temp.type().changeParameterType(0 , Object.class));getterMethodHandle = temp.asType(temp.type().changeReturnType(Object.class));}public Object executeGetter(Object bean) {return getterMethodHandle.invokeExact(bean);}}不幸的是, MethodHandle甚至比 OpenJDK 8中的反射還要慢 。每次操作花費(fèi)6.1納秒,因此比直接訪問(wèn)慢132%。
Benchmark Mode Cnt Score Error Units =================================================== DirectAccess avgt 60 2.667 ± 0.028 ns/op Reflection avgt 60 5.511 ± 0.081 ns/op MethodHandle avgt 60 6.188 ± 0.059 ns/op StaticMethodHandle avgt 60 5.481 ± 0.069 ns/op話雖如此,如果MethodHandle在靜態(tài)字段中,則每次操作僅需5.5納秒,這仍然與反射一樣慢 。 此外,對(duì)于大多數(shù)框架而言,這是無(wú)法使用的。 例如,JPA實(shí)現(xiàn)可能需要反映n類( Person , Company , Order等)的m getter( getName() , getAddress() , getBirthDate() ,...),因此JPA實(shí)現(xiàn)如何有n * m靜態(tài)字段,在編譯時(shí)不知道n和m ?
我確實(shí)希望MethodHandle在將來(lái)的Java版本中能夠像直接訪問(wèn)一樣快,從而取代對(duì)...的需求。
使用javax.tools.JavaCompiler生成的代碼
在Java中,可以在運(yùn)行時(shí)編譯和運(yùn)行生成的Java代碼。 因此,使用javax.tools.JavaCompiler API,我們可以在運(yùn)行時(shí)生成直接訪問(wèn)代碼:
public abstract class MyAccessor {public static MyAccessor generate() {final String String fullClassName = "x.y.generated.MyAccessorPerson$getName";final String source = "package x.y.generated;\n"+ "public final class MyAccessorPerson$getName extends MyAccessor {\n"+ " public Object executeGetter(Object bean) {\n"+ " return ((Person) object).getName();\n"+ " }\n"+ "}";JavaFileObject fileObject = new ...(fullClassName, source);JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();ClassLoader classLoader = ...;JavaFileManager javaFileManager = new ...(..., classLoader)CompilationTask task = compiler.getTask(..., javaFileManager, ..., singletonList(fileObject));boolean success = task.call();...Class compiledClass = classLoader.loadClass(fullClassName);return compiledClass.newInstance();}// Implemented by the generated subclasspublic abstract Object executeGetter(Object object);}有關(guān)如何使用javax.tools.JavaCompiler更多信息,請(qǐng)參見(jiàn)本文或本文的 第2頁(yè) 。 除了javax.tools之外,類似的方法也可以使用ASM或CGLIB,但是這些方法會(huì)推斷出額外的依賴性,并且可能會(huì)產(chǎn)生不同的性能結(jié)果。
無(wú)論如何, 生成的代碼與直接訪問(wèn)一樣快 :
Benchmark Mode Cnt Score Error Units =================================================== DirectAccess avgt 60 2.667 ± 0.028 ns/op GeneratedCode avgt 60 2.745 ± 0.025 ns/op因此,當(dāng)我再次在OptaPlanner中運(yùn)行該完全相同的Traveling Salesman問(wèn)題時(shí),這一次使用代碼生成來(lái)訪問(wèn)計(jì)劃變量, 因此總分計(jì)算速度提高了18% 。 并且分析(使用采樣)看起來(lái)也更好:
請(qǐng)注意,在正常使用情況下,由于大量CPU需要實(shí)際復(fù)雜的分?jǐn)?shù)計(jì)算,因此性能提升幾乎是無(wú)法檢測(cè)到的...
運(yùn)行時(shí)代碼生成的唯一缺點(diǎn)是,它會(huì)導(dǎo)致可觀的引導(dǎo)成本,特別是如果生成的代碼未進(jìn)行批量編譯時(shí)。 因此,我仍然希望有一天MethodHandles能夠像直接訪問(wèn)一樣快,只是為了避免啟動(dòng)成本。
結(jié)論
在此基準(zhǔn)測(cè)試中,反射和MethodHandles的速度是OpenJDK 8中直接訪問(wèn)的兩倍,但是生成的代碼的速度是直接訪問(wèn)的速度。
Benchmark Mode Cnt Score Error Units =================================================== DirectAccess avgt 60 2.667 ± 0.028 ns/op Reflection avgt 60 5.511 ± 0.081 ns/op MethodHandle avgt 60 6.188 ± 0.059 ns/op StaticMethodHandle avgt 60 5.481 ± 0.069 ns/op GeneratedCode avgt 60 2.745 ± 0.025 ns/op翻譯自: https://www.javacodegeeks.com/2018/01/java-reflection-much-faster.html
java 反射 速度
總結(jié)
以上是生活随笔為你收集整理的java 反射 速度_Java反射,但速度更快的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 魏无羡的父母分别是谁(魏无羡的父母什么厉
- 下一篇: 顺丰快递微信怎么下单 顺丰快递代下教程