Comments 22
Есть еще один способ, не волшебный, правда, и не всегда применимый, но мега-крутой - compile-time кодогенерация и post-compile weaving. Последний вообще позволяет получить перфоманс "как будто бы сам написал".
Погрузится в пучины LambdaMetafactory можно тут https://github.com/FasterXML/jackson-modules-base/tree/master/blackbird
Заодно переедете с устаревшего Afterburner на новый Blackbird.
Спасибо за статью. Почему Вы не используете фреймворк для микробенчмаркинга (например, jmh)? Кроме того в 18ой джаве core reflections переписали на var handles.
https://openjdk.java.net/jeps/416
Про var handles не знал, спасибо. Что насчет jmh, в данном случае его использование показалось излишним, все измерения сводились к вызову нескольких методов, да и точности используемого подхода вполне хватает.
Дело не в точности, а в достоверности и воспроизводимости. System.nanoTime с кодом в цикле может показать что угодно, т.к. не учитывается оптимизации, jit, code-кеши и прочее. JMH же создан так, чтобы абстрагироваться от всего такого и дать прикладному разработчику инструмент для написания достоверных (хотя бы для его железа) бенчмарков. Так что рекомендую в будущем использовать его.
Так они же не в цикле, а до-после... Ну да, могут врать, но не порядки же
А кеширование как вариант почему не рассматривается?
Каким образом кэширование поможет ускорить рефлективные вызовы метода? Имхо, максимум можно кэшировать получение объекта java.lang.reflect.Method, но это даст выигрыш лишь в скорости подготовки вызова.
Если вы говорите о кэшировании сгенерированных прокси-классов, чтобы не создавать их заново для одной и той же связки класс-метод, то это уже по сути детали конкретной реализации, которые были опущены, чтобы не загружать обзорную статью.
P. S. Если имеется ввиду кэширование получаемой из метода информации, дабы предотвратить излишние вызовы, то это материал для совершенно другой статьи.
Ну нет же, JNI тут ни при чем. Современные JVM содержат интринсики для методов рефлексии. Например, вот так обрабатываются методы типа java.lang.Class.isInterface:
https://github.com/openjdk/jdk/blob/master/src/hotspot/share/opto/library_call.cpp#L3565
Ну смотрите. Байт-код можно либо интепретировать, либо компилировать в платформенный код. В обоих случаях мы можем вместо какого-то метода JVM вызвать просто какую-нибудь функцию самой JVM, чем и являются интринсики.
Ну а JNI - это сложнейший комбайн, который поддерживает потокобезопасность, открывает во внешний мир API JVM и делает еще кучу вещей, необходимых для стабильного FFI.
Рефлексия в Джаве помимо пользы может нести в себе кучу багов, которые потом хрен поймаешь. А так же это - отличный инструмент в руках хакера/злоумышленника. Получить доступ можно к чему угодно.
Занимательные замеры времени. Вывод же несколько ограниченный. Вся разница получается за счёт компиляции метода add в одну asm инструкцию вместо нескольких десятков для reflection-а. Ну а промежуточные значения есть лишь показатель способности методом тыка подсказать компилятору, как же правильно компилировать.
Ну и про "тяжесть" рефлексии. Просто не надо писать критический код с её использованием. И в подавляющем большинстве случаев для этого нет никакой необходимости. А некритический код, исполняемый за 1 микросекунду или за 10 наносекунд - абсолютно ни на что не влияет.
Странно, что нет теста с Unsafe... Если вы уже решили прыгнуть в гигантский цикл(то есть массовые операции) то очевидно, что дальше только хардкор... И это не про суррогатный тест с рефлекшан
Как будто в этой строке не хватает использования i
lambda.call(arguments);
А почему вы считаете, что измерили именно вызов метода 'add'? Он может заинлайнится, цикл может быть оптимизирован, а, учитывая, что поле 'value' не читается, не удивлюсь, если окажется, что и вызов метода вместе с циклом были просто удалены
Ускоряем java-рефлексию в 2022