Есть ли способ дополнительно оптимизировать вызов рефлексивного метода Java?

Мне интересно, есть ли какие-либо дополнительные оптимизации, которые я могу реализовать для повышения скорости рефлексивных вызовов в Java. Не то чтобы производительность была непомерно высокой, но я начинаю нервничать, когда думаю о том, что какой-то фрагмент кода в библиотеке, которую я пишу, реализуется где-то в тесном цикле.

Рассмотрим вспомогательный метод для рефлексивного вызова:

public static Object invoke(Object targetObject, String methodName, Object[] arguments, Class<?>[] signature)

Основная операция

return method.invoke(targetObject, arguments);

В целях оптимизации производительности я кэширую метод, используя хэш класса целевого объекта, имя метода и подпись (код которого может быть улучшен), но помимо этого, могу ли я что-нибудь еще сделать? Я слышал ссылки на некоторые ранние реализации InvokeDynamic, которые звучат многообещающе, но я просто предположил, что они, вероятно, еще не применимы, и отказался от собственных манипуляций с байтовым кодом, так как хотел бы, чтобы утилита была простой. (только быстро).

Ваше здоровье.


person Nicholas    schedule 05.01.2009    source источник


Ответы (5)


Комментарии ниже относятся к реализации Sun, в частности OpenJDK 6. Ваш опыт может отличаться от других реализаций платформы Java.

java.lang.Class выполняет некоторое кэширование, поэтому реализация собственного кеша может не сильно улучшить ситуацию. Выполняйте временные тесты с ручным кэшированием и без него.

Фактический механизм вызова также оптимизирован. Первые 15 запусков (по умолчанию) вашего отраженного метода вызываются с использованием JNI; после этого создается байт-код, и вызов этого отраженного метода будет работать так же, как вызов этого метода непосредственно в коде Java.

person Chris Jester-Young    schedule 05.01.2009
comment
Вау, я этого не знал. Я сделаю эти временные тесты. Есть ли способ изменить значение 15 по умолчанию? Есть ли другой способ убедить компилятор начать работу раньше? - person Nicholas; 06.01.2009
comment
Просто установите для свойства sun.reflect.inflationThreshold желаемое число. - person Chris Jester-Young; 06.01.2009

Я провел несколько тестов на основе ответа Криса Джестера-Янга и, используя параметры подробный, я определенно заметил, что компилятор предпринял некоторые действия вокруг 15-го вызова. Трудно сказать, есть ли большая разница в производительности без более сложного теста, но он убедителен. Вот результат:

Test# 0
Test# 1
Test# 2
Test# 3
Test# 4
Test# 5
Test# 6
Test# 7
Test# 8
Test# 9
Test# 10
Test# 11
Test# 12
Test# 13
Test# 14
[Loaded sun.reflect.ClassFileConstants from C:\jdk1.5.0_06\jre\lib\rt.jar]
[Loaded sun.reflect.AccessorGenerator from C:\jdk1.5.0_06\jre\lib\rt.jar]
[Loaded sun.reflect.MethodAccessorGenerator from C:\jdk1.5.0_06\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVectorFactory from C:\jdk1.5.0_06\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVector from C:\jdk1.5.0_06\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVectorImpl from C:\jdk1.5.0_06\jre\lib\rt.jar]
[Loaded sun.reflect.ClassFileAssembler from C:\jdk1.5.0_06\jre\lib\rt.jar]
[Loaded sun.reflect.UTF8 from C:\jdk1.5.0_06\jre\lib\rt.jar]
[Loaded java.lang.Void from C:\jdk1.5.0_06\jre\lib\rt.jar]
[Loaded sun.reflect.Label from C:\jdk1.5.0_06\jre\lib\rt.jar]
[Loaded sun.reflect.Label$PatchInfo from C:\jdk1.5.0_06\jre\lib\rt.jar]
[Loaded java.util.AbstractList$Itr from C:\jdk1.5.0_06\jre\lib\rt.jar]
[Loaded sun.reflect.MethodAccessorGenerator$1 from C:\jdk1.5.0_06\jre\lib\rt.jar]
[Loaded sun.reflect.ClassDefiner from C:\jdk1.5.0_06\jre\lib\rt.jar]
[Loaded sun.reflect.ClassDefiner$1 from C:\jdk1.5.0_06\jre\lib\rt.jar]
[Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]
Test# 15
Test# 16
Test# 17

Я полагаю, что бизнес InvokeDynamic не привлекает слишком много разработчиков из-за ускорения / устранения рефлексии.

Спасибо, Крис.

person Nicholas    schedule 09.01.2009

Вы определенно захотите отразить объект метода только один раз (вероятно, как частный статический) и использовать его для вызова, а не отражать его каждый раз. Не беспокойтесь о карте кеша, если вы не знаете имя во время компиляции.

Если это разумно в вашем контексте, вы можете захотеть закрепить и повторно использовать объект массива аргументов (не забудьте обнулить элементы массива при выходе из метода, чтобы предотвратить временное запрещение сборки мусора).

Если он всегда вызывается с одними и теми же параметрами (что маловероятно), вы можете повесить на них параметры и (массив аргументов с его значениями) повторно использовать их.

Я бы не стал делать ничего, кроме этого, по причинам, уже указанным в других ответах.

person Lawrence Dol    schedule 06.01.2009

Преждевременная оптимизация - это вообще плохо. Несмотря ни на что, ваша производительность по-прежнему будет во много раз выше, чем у динамического языка, поэтому я действительно не стал бы беспокоиться об этом, кроме документирования того факта, что он использует отражение и, следовательно, может быть неоптимальным.

Кроме того, есть большая вероятность, что сейчас или в будущем Java оптимизирует байт-код, так что вызов не будет стоить больше, чем вызов метода при использовании в цикле. Ваша «Оптимизация» может фактически помешать компиляторам делать что-то подобное. (Я знаю, что говорю нечетко, но это случилось - ОЧЕНЬ МНОГО).

person Bill K    schedule 05.01.2009
comment
Смирившись, я обнаружил, что непубличные методы требуют лазания по дереву наследования для поиска методов в родителях, поэтому я обнаружил, что в некоторых случаях требуется вызывать метод get [Declared] несколько раз. Кроме того, мне нужно вызвать setAccessible (true) только один раз. Я все еще не на базе? - person Nicholas; 06.01.2009
comment
Я не думаю, что вы ошиблись, это интересный анализ, но, если вы не обнаружите необходимости, вы должны почти всегда использовать наиболее удобочитаемое, простое и прямое решение, которое вы можете придумать. - person Bill K; 06.01.2009

Если стоимость вызова составляет менее 10% от стоимости того, что происходит в методе, вряд ли стоит беспокоиться.

Вы можете определить это, выполнив его 10 ^ 6 раз в цикле, с внутренним составом рутины и без него. Измерьте время с помощью секундомера, чтобы секунды переводились в микросекунды.

person Mike Dunlavey    schedule 09.01.2009
comment
Стоимость в процентах будет полностью зависеть от того, что на самом деле делает этот метод. - person Nicole; 13.01.2011
comment
@ Ренезис: Верно. Это моя точка зрения. Время, затрачиваемое на вход в метод и выход из него, может быть небольшим по сравнению со временем, затрачиваемым на полезную работу внутри метода. Если это так, оптимизация входа и выхода может помочь лишь немного в процентном отношении. - person Mike Dunlavey; 13.01.2011
comment
Я хочу сказать, что если сам метод был чрезвычайно легким (одно присвоение или просто возвращение значения), тип самого вызова метода может иметь большое влияние (сгруппированный по множеству вызовов). - person Nicole; 13.01.2011
comment
@Renesis: Тогда я думаю, что мы на одной странице. - person Mike Dunlavey; 14.01.2011