Подпишитесь на наш Telegram-канал, чтобы всегда быть в курсе важных обновлений! Перейти

Гайд [Java] Учимся писать свою шину событий (Event Bus)

Советую почитать про то как JIT оптимизирует вызовы в особенности invokedynamic. Так же существует такая вещь как LambdaMetaFactory.
Ладно если-б было какое-то объяснение...
Почитал про JIT, понял что ошибался на счет лямбд и конкретно Metafactory. Решил прогнать грубый бенчмарк и... На самом деле удивился что код практически идентичен по производительности моему примеру который я выкидывал на гит(если предварительно убрать локи, что я кстати тоже понял только при прогонах бенчмарка).

Докинув в свою шину вызовы методов через invokeExact - получил небольшой буст и по цифрам получилось даже чуть получше чем с invoke обёрнутым через metafactory. Я так понимаю что в любом случае что invokedynamic что invokevirtual JIT компилирует до одного и того-же примерно "места", где перф фактически не сильно отличается. Это всё чисто мои логические выводы, мб попозже спрошу чела который действительно разбирается в нюансах работы JIT, но по сути как будто бы тут не то что-бы есть разница между LambdaMetafactory и простыми классами заглушками. Единственное что кажется действительно даст буст - так это использовать сгенерированные классы, тут я бенчмарк не делал, но думаю что прирост не должен быть супер большим, т.к. фактически его отличие это отсутствие MethodHandles лукапов и вызова через этот-же MethodHandles

p.s. забыл приложить бенчмарк(опять-же грубый), в случае UebusBenchmark - шина событий без Reentrant Lock'ов
Benchmark(iterations)ModeCntScoreErrorUnits
MetafactoryBusBenchmark.benchmark100thrpt251134,768± 29,950ops/us
MetafactoryBusBenchmark.benchmark200thrpt251064,786± 58,833ops/us
MetafactoryBusBenchmark.benchmark1000thrpt251075,534± 6,610ops/us
UebusBenchmark.benchmark100thrpt251200,214± 4,959ops/us
UebusBenchmark.benchmark200thrpt251201,587± 5,934ops/us
UebusBenchmark.benchmark1000thrpt251212,252± 11,764ops/us
 
Последнее редактирование:
но думаю что прирост не должен быть супер большим, т.к. фактически его отличие это отсутствие MethodHandles лукапов и вызова через этот-же MethodHandles
так MethodHandle не даёт супер большого прироста в производительности
я особо не знаю, как это работает под капотом, пытался вникнуть, но в итоге забил. вот что скажу точно, так это если хранить его как static final поле, то производительность - да, возрастёт, но иначе нет
 
так MethodHandle не даёт супер большого прироста в производительности
я особо не знаю, как это работает под капотом, пытался вникнуть, но в итоге забил. вот что скажу точно, так это если хранить его как static final поле, то производительность - да, возрастёт, но иначе нет
Он и не должен же, у него есть свой оверхед и он ни в какие рамки же не пойдёт против пары инструкции с заглушками сгенеренными через тот-же ASM которые по сути просто будут вызывать метод напрямую

Тут просто как я понял чел думал что если обернуть вызов метода через MethodHandle в объект созданный через LambdaMetafactory то будет какой-то прирост, но по идее будто JIT это всё-равно всё компилирует в похожий(практически идентичный по производительности) код, вот выигрыша я и не увидел
 
Ладно если-б было какое-то объяснение...
Почитал про JIT, понял что ошибался на счет лямбд и конкретно Metafactory. Решил прогнать грубый бенчмарк и... На самом деле удивился что код практически идентичен по производительности моему примеру который я выкидывал на гит(если предварительно убрать локи, что я кстати тоже понял только при прогонах бенчмарка).

Докинув в свою шину вызовы методов через invokeExact - получил небольшой буст и по цифрам получилось даже чуть получше чем с invoke обёрнутым через metafactory. Я так понимаю что в любом случае что invokedynamic что invokevirtual JIT компилирует до одного и того-же примерно "места", где перф фактически не сильно отличается. Это всё чисто мои логические выводы, мб попозже спрошу чела который действительно разбирается в нюансах работы JIT, но по сути как будто бы тут не то что-бы есть разница между LambdaMetafactory и простыми классами заглушками. Единственное что кажется действительно даст буст - так это использовать сгенерированные классы, тут я бенчмарк не делал, но думаю что прирост не должен быть супер большим, т.к. фактически его отличие это отсутствие MethodHandles лукапов и вызова через этот-же MethodHandles

p.s. забыл приложить бенчмарк(опять-же грубый), в случае UebusBenchmark - шина событий без Reentrant Lock'ов
Benchmark(iterations)ModeCntScoreErrorUnits
MetafactoryBusBenchmark.benchmark100thrpt251134,768± 29,950ops/us
MetafactoryBusBenchmark.benchmark200thrpt251064,786± 58,833ops/us
MetafactoryBusBenchmark.benchmark1000thrpt251075,534± 6,610ops/us
UebusBenchmark.benchmark100thrpt251200,214± 4,959ops/us
UebusBenchmark.benchmark200thrpt251201,587± 5,934ops/us
UebusBenchmark.benchmark1000thrpt251212,252± 11,764ops/us
Такие результаты, объясняются тем, что реализация прикрепленная мною выше не совсем корректная(я буквально написал ее без всяких тестов, не запускал даже), посмотри как это делается в бакките новом.
 
Он и не должен же, у него есть свой оверхед и он ни в какие рамки же не пойдёт против пары инструкции с заглушками сгенеренными через тот-же ASM которые по сути просто будут вызывать метод напрямую

Тут просто как я понял чел думал что если обернуть вызов метода через MethodHandle в объект созданный через LambdaMetafactory то будет какой-то прирост, но по идее будто JIT это всё-равно всё компилирует в похожий(практически идентичный по производительности) код, вот выигрыша я и не увидел
прирост там есть, причем скорость намного выше, чем у метод хендлов. как я упомянал ранее, метод хендлы медленные довольно сами по себе, реализация у них хромает в самой жвм, они выходят даже медленней, чем обычная рефлексия. но, всё таки я не понимаю, зачем использовать условные лямбды, если можно просто генерировать класс в рантайме.. для обоих способов нужна динамическая загрузка классов, в чём преимущество лямбд перед созданием прокси класса? не ясно, ибо очевидно, что invokedynamic будет проигрывать прямому invokevirtual в интерпретации и скомпилированном коде. единственное, что на ум приходит, так это более компактная и приятная для глаза читабельность кода
 
Такие результаты, объясняются тем, что реализация прикрепленная мною выше не совсем корректная(я буквально написал ее без всяких тестов, не запускал даже), посмотри как это делается в бакките новом.
> Посмотрел PaperSpigot в активной стадии разработки
Пожалуйста, авторизуйтесь для просмотра ссылки.
Пожалуйста, авторизуйтесь для просмотра ссылки.

Пожалуйста, авторизуйтесь для просмотра ссылки.


1764915549274.png


При чём мало этого, вместо Metafactory они для этого используют клонированые классы которые создают через те же MethodHandles и ClassData. Может конечно разница какая-то да будет из-за отсутствия checkcast которые плодятся когда компилятор опускает дженерики(конкретно случай моей библиотеки), но на бенчмарке даже с этим оно как-то да умудряется выигрывать...

реализация у них хромает в самой жвм, они выходят даже медленней, чем обычная рефлексия.
Даже если это действительно так, то их спасают JIT оптимизации, иначе бы их не использовали бы конкретно в нашем случае
С генерацией уже давно разобрались, хз зачем повторяться, просто что я её упоминал как более адекватный аналог
 
Даже если это действительно так, то их спасают JIT оптимизации
с чего бы это жит оптимизировал их? в случае с static final полем всё ясно, оно просто никогда не изменит свое значение, потому-что final и потому-что static, а java mirror живёт до тех пор пока живёт класс. ну а если метод хендл получается динамически? это сколько костыльного кода нужно приписать, чтобы жит "мог доверить" объект. а если он юзается не в static методе, то что будет?
 
с чего бы это жит оптимизировал их? в случае с static final полем всё ясно, оно просто никогда не изменит свое значение, потому-что final и потому-что static, а java mirror живёт до тех пор пока живёт класс. ну а если метод хендл получается динамически? это сколько костыльного кода нужно приписать, чтобы жит "мог доверить" объект. а если он юзается не в static методе, то что будет?
А с чего ему их не оптимизировать? Если тебе так интересно -
Пожалуйста, авторизуйтесь для просмотра ссылки.

Что же касается использования вне статического контекста - это буквально пример использования моей библиотеки в бенчмарке: создал лукап, закешировал MethodHandle, вызываешь его когда нужно.
 
А с чего ему их не оптимизировать? Если тебе так интересно -
Пожалуйста, авторизуйтесь для просмотра ссылки.

Что же касается использования вне статического контекста - это буквально пример использования моей библиотеки в бенчмарке: создал лукап, закешировал MethodHandle, вызываешь его когда нужно.
я говорю про более глубокое. например, проверка на сигнатуру, аргументы
 
я говорю про более глубокое. например, проверка на сигнатуру, аргументы
Эта конкретно тема посвящена шинам событий а не MethodHandle/JIT оптимизациям и прочему
Для срачей касательно перформанса и применения тех или иных штук нужно идти в соответствующий раздел и обсуждать это в отдельной теме, иначе это будет оффтоп
 
Эта конкретно тема посвящена шинам событий а не MethodHandle/JIT оптимизациям и прочему
Для срачей касательно перформанса и применения тех или иных штук нужно идти в соответствующий раздел и обсуждать это в отдельной теме, иначе это будет оффтоп
так, шины событий между собой отличаются как раз таки разным уровнем оптимизации, иначе зачем вообще этот пост делать было? звучит так, будто хочешь улизнуть с этой темы, честно
 
Назад
Сверху Снизу