Бржа алтернатива Јава Рефлецтиону

У чланку Спецификација образац, због здраве памети, нисам споменуо основну компоненту која би то лепо учинила. Сада ћу разрадити мало више око класе ЈаваБеанУтил, коју сам ставио да очитам вредност за дати fieldNameиз одређеног javaBeanObject, за који се том приликом испоставило да је ФкТрансацтион.

Можете лако тврдити да сам у основи могао да користим Апацхе Цоммонс БеанУтилс или једну од његових алтернатива да бих постигао исти резултат. Али занимало ме је да упрљам своје руке нечим другачијим за шта сам знао да ће бити бржи од било које библиотеке изграђене на врху надалеко познатог Јава Рефлецтион-а.

Омогућавање технике која се користи за избегавање врло спорог одраза је invokedynamicинструкција битецоде-а. Укратко, invokedynamic(или „инди“) је била најбоља ствар која је уведена у Јави 7 како би се отворио пут за примену динамичких језика на врху ЈВМ-а кроз позивање динамичке методе. Такође је касније омогућио ламбда израз и референцу методе у Јави 8, као и спајање низова у Јави 9 да би имали користи од тога.

Укратко, техника коју ћу боље описати у наставку користи ЛамбдаМетафацтори и МетходХандле како би динамички креирала имплементацију функције. Његова једнострука метода делегира позив стварној циљној методи са кодом дефинисаним унутар ламбда тела.

Метода о којој је овде реч је стварна метода гетера која има директан приступ пољу које желимо да читамо. Такође, требало би да кажем ако сте прилично упознати са лепим стварима које су се појавиле у оквиру Јава 8, наћи ћете да су доњи исечци кода прилично једноставни за разумевање. У супротном, на први поглед може бити незгодно.

Завирите у домаћи ЈаваБеанУтил

Следећи метод је услужни програм који се користи за читање вредности из поља ЈаваБеан. Потребан је објект ЈаваБеан и једно fieldAили чак угнежђено поље одвојено тачкама , на пример,nestedJavaBean.nestedJavaBean.fieldA

За оптималне перформансе, меморишем динамички створену функцију која је стварни начин читања садржаја датог fieldName. Дакле, унутар getCachedFunctionметоде, као што видите горе, постоји брза путања која користи ЦлассВалуе за кеширање, а постоји и спора createAndCacheFunctionпутања која се извршава само ако до сада ништа није кеширано .

Спора путања ће се у основи делегирати на createFunctionsметоду која враћа листу функција које треба смањити ланцем помоћу њих Function::andThen. Када су функције повезане ланцима, можете замислити неку врсту угнежђених позива попут getNestedJavaBean().getNestedJavaBean().getFieldA(). Коначно, након уланчавања, једноставно стављамо смањену функцију у cacheAndGetFunctionметоду позивања кеш меморије .

Удубљујући мало више у полагани пут стварања функције, морамо појединачно да се крећемо кроз pathпроменљиву поља раздвајањем према доле:

Горе наведени createFunctionsметод делегира појединца fieldNameи тип носиоца класе на createFunctionметоду, који ће лоцирати потребан гетер на основу javaBeanClass.getDeclaredMethods(). Једном када се лоцира, пресликава се на објекат Тупле (објект из библиотеке Вавр), који садржи тип повратка геттер методе и динамички креирану функцију у којој ће се понашати као да је то заправо стварна геттер метода.

Ово мапирање корпица врши createTupleWithReturnTypeAndGetterсе заједно са createCallSiteметодом како следи:

У горње две методе користим константу LOOKUPпозвану, која је једноставно референца на МетходХандлес.Лоокуп. Уз то, могу да створим директну ручку методе засновану на претходно лоцираној методи гетера. И на крају, креирана МетходХандле се преноси на createCallSiteметоду при којој се ламбда тело за функцију производи помоћу ЛамбдаМетафацтори. Одатле, на крају, можемо добити инстанцу ЦаллСите, која је носилац функције.

Имајте на уму да ако бих желео да имам посла са постављачима, могао бих да користим сличан приступ коришћењем БиФунцтион-а уместо Фунцтион-а.

Бенцхмарк

Да бих измерио добитак у перформансама, користио сам непрестано ЈМХ (Јава Мицробенцхмарк Харнесс), који ће вероватно бити део ЈДК 12. Као што можда знате, резултати су везани за платформу, па ћу за референцу користи један 1x6 i5-8600K 3.6GHzи Linux x86_64као и Oracle JDK 8u191и GraalVM EE 1.0.0-rc9.

За поређење, користио сам Апацхе Цоммонс БеанУтилс, познату библиотеку за већину Јава програмера, и једну од његових алтернатива названу Јодд БеанУтил која тврди да је скоро 20% бржа.

Референтни сценарио постављен је на следећи начин:

Референтна вредност зависи од тога колико ћемо дубоко добити неку вредност према четири различита нивоа наведена горе. За сваку fieldName, ЈМХ ће извести 5 итерација од по 3 секунде да би загрејао ствари, а затим 5 итерација од по 1 секунду за стварно мерење. Сваки сценарио ће се затим поновити 3 пута да би се разумно прикупили метрички подаци.

Резултати

Почнимо са резултатима прикупљеним у току JDK 8u191:

Најгори сценарио који користи invokedynamicприступ је много бржи од најбржег сценарија из друге две библиотеке. То је огромна разлика и ако сумњате у резултате, увек можете да преузмете изворни код и поиграте се како желите.

Сада, да видимо како исти бенчмарк ради са GraalVM EE 1.0.0-rc9

Комплетне резултате можете погледати овде са лепим ЈМХ Визуализатором.

Посматрања

Огромна је разлика у томе што ЈИТ-компајлер зна CallSiteи то MethodHandleврло добро и зна како их прилично добро уградити за разлику од приступа рефлексији. Такође, можете видети колико је ГраалВМ обећавајући. Његов компајлер ради заиста сјајан посао, способан за велико побољшање перформанси за приступ рефлексији.

Ако сте радознали и желите да играте даље, подстичем вас да повучете изворни код са мог Гитхуб-а. Имајте на уму да вас не охрабрујем да сами радите домаће производе JavaBeanUtilи користите их у производњи. Мој циљ овде је једноставно да представим свој експеримент и могућности које можемо добити invokedynamic.