Ответы:
Да, конечно. Поиск класса с помощью отражения по величине , дороже.
Цитирую документацию Java по рефлексии :
Поскольку отражение включает в себя типы, которые динамически разрешаются, некоторые оптимизации Java виртуальной машины не могут быть выполнены. Следовательно, отражающие операции имеют более низкую производительность, чем их неотражающие аналоги, и их следует избегать в разделах кода, которые часто вызываются в чувствительных к производительности приложениях.
Вот простой тест, который я взломал за 5 минут на моей машине под управлением Sun JRE 6u10:
public class Main {
public static void main(String[] args) throws Exception
{
doRegular();
doReflection();
}
public static void doRegular() throws Exception
{
long start = System.currentTimeMillis();
for (int i=0; i<1000000; i++)
{
A a = new A();
a.doSomeThing();
}
System.out.println(System.currentTimeMillis() - start);
}
public static void doReflection() throws Exception
{
long start = System.currentTimeMillis();
for (int i=0; i<1000000; i++)
{
A a = (A) Class.forName("misc.A").newInstance();
a.doSomeThing();
}
System.out.println(System.currentTimeMillis() - start);
}
}
С этими результатами:
35 // no reflection
465 // using reflection
Имейте в виду, что поиск и создание экземпляров выполняются вместе, а в некоторых случаях поиск может быть подвергнут рефакторингу, но это только базовый пример.
Даже если вы просто создадите экземпляр, вы все равно получите удар по производительности:
30 // no reflection
47 // reflection using one lookup, only instantiating
Опять YMMV.
Да, это медленнее.
Но помните чертово правило № 1 - ОПТИМИЗАЦИЯ ПРЕДВАРИТЕЛЬНОЙ СИЛЫ - КОРЕНЬ ВСЕГО ЗЛА
(Ну, может быть связано с № 1 для СУХОГО)
Клянусь, если бы кто-нибудь подошел ко мне на работе и спросил меня об этом, я бы очень внимательно следил за их кодом в течение следующих нескольких месяцев.
Вы никогда не должны оптимизировать, пока не будете уверены, что вам это нужно, до тех пор просто пишите хороший, читаемый код.
О, и я не имею в виду писать глупый код тоже. Просто подумайте о том, как сделать это самым чистым способом - без копирования и вставки и т.д. Плохое программирование)
Меня пугает, когда я слышу подобные вопросы, но потом я забываю, что все должны пройти изучение всех правил самостоятельно, прежде чем они действительно получат это. Вы получите это после того, как потратили человек-месяц на отладку чего-то, что кто-то «оптимизировал».
РЕДАКТИРОВАТЬ:
Интересная вещь произошла в этой теме. Посмотрите ответ № 1, это пример того, насколько мощным компилятор является в оптимизации вещей. Тест полностью недействителен, потому что неотражающий экземпляр может быть полностью исключен.
Урок? Никогда не оптимизируйте, пока вы не написали чистое, аккуратно закодированное решение и не доказали, что оно слишком медленное.
Вы можете обнаружить, что A a = new A () оптимизируется JVM. Если вы помещаете объекты в массив, они не так хорошо работают. ;) Следующие принты ...
new A(), 141 ns
A.class.newInstance(), 266 ns
new A(), 103 ns
A.class.newInstance(), 261 ns
public class Run {
private static final int RUNS = 3000000;
public static class A {
}
public static void main(String[] args) throws Exception {
doRegular();
doReflection();
doRegular();
doReflection();
}
public static void doRegular() throws Exception {
A[] as = new A[RUNS];
long start = System.nanoTime();
for (int i = 0; i < RUNS; i++) {
as[i] = new A();
}
System.out.printf("new A(), %,d ns%n", (System.nanoTime() - start)/RUNS);
}
public static void doReflection() throws Exception {
A[] as = new A[RUNS];
long start = System.nanoTime();
for (int i = 0; i < RUNS; i++) {
as[i] = A.class.newInstance();
}
System.out.printf("A.class.newInstance(), %,d ns%n", (System.nanoTime() - start)/RUNS);
}
}
Это говорит о том, что на моей машине разница составляет около 150 нс.
Class.getDeclaredMethod
), а затем позвоню Method.invoke
несколько раз? Я использую отражение один раз или столько раз, сколько я его вызываю? Последующий вопрос, а что если вместо Method
него это Constructor
сделать Constructor.newInstance
несколько раз?
Если действительно нужно что-то быстрее, чем отражение, и это не просто преждевременная оптимизация, то генерация байт-кода с помощью ASM вариант или библиотеки более высокого уровня - вариант. Генерация байт-кода в первый раз медленнее, чем просто использование отражения, но как только байт-код сгенерирован, он работает так же быстро, как обычный код Java, и будет оптимизирован компилятором JIT.
Некоторые примеры приложений, которые используют генерацию кода:
Вызов методов на прокси, сгенерированных CGLIB , немного быстрее, чем динамические прокси Java , потому что CGLIB генерирует байт-код для своих прокси, но динамические прокси используют только отражение ( я измерил CGLIB примерно в 10 раз быстрее при вызовах методов, но создание прокси было медленнее).
JSerial генерирует байт-код для чтения / записи полей сериализованных объектов вместо использования отражения. На сайте JSerial есть несколько тестов .
Я не уверен на 100% (и сейчас мне не хочется читать исходники), но я думаю, что Guice генерирует байт-код для внедрения зависимостей. Поправьте меня если я ошибаюсь.
«Значительный» полностью зависит от контекста.
Если вы используете рефлексию для создания одного объекта-обработчика на основе какого-либо файла конфигурации, а затем тратите оставшееся время на выполнение запросов к базе данных, то это не имеет значения. Если вы создаете большое количество объектов с помощью отражения в узком цикле, то да, это важно.
В общем, гибкость дизайна (где это необходимо!) Должна стимулировать использование отражения, а не производительности. Однако, чтобы определить, является ли производительность проблемой, вам нужно профилировать, а не получать произвольные ответы от дискуссионного форума.
С отражением есть некоторые издержки, но на современных виртуальных машинах они намного меньше, чем раньше.
Если вы используете отражение для создания каждого простого объекта в вашей программе, значит что-то не так. Использование его изредка, когда у вас есть веская причина, не должно быть проблемой вообще.
Да, при использовании Reflection наблюдается снижение производительности, но возможный обходной путь оптимизации - кэширование метода:
Method md = null; // Call while looking up the method at each iteration.
millis = System.currentTimeMillis( );
for (idx = 0; idx < CALL_AMOUNT; idx++) {
md = ri.getClass( ).getMethod("getValue", null);
md.invoke(ri, null);
}
System.out.println("Calling method " + CALL_AMOUNT+ " times reflexively with lookup took " + (System.currentTimeMillis( ) - millis) + " millis");
// Call using a cache of the method.
md = ri.getClass( ).getMethod("getValue", null);
millis = System.currentTimeMillis( );
for (idx = 0; idx < CALL_AMOUNT; idx++) {
md.invoke(ri, null);
}
System.out.println("Calling method " + CALL_AMOUNT + " times reflexively with cache took " + (System.currentTimeMillis( ) - millis) + " millis");
приведет к:
[java] вызов метода 1000000 раз рефлексивно с поиском занял 5618 миллис
[java] вызов метода 1000000 раз рефлексивно с кешем занял 270 миллис
Отражение медленное, хотя распределение объектов не так безнадежно, как другие аспекты отражения. Достижение эквивалентной производительности с помощью основанной на отражении реализации требует от вас написания кода, чтобы jit мог определить, какой класс создается. Если идентичность класса не может быть определена, тогда код распределения не может быть встроен. Хуже того, escape-анализ завершается неудачно, и объект не может быть размещен в стеке. Если вам повезет, профилирование во время выполнения JVM может прийти на помощь, если этот код перегреется, и может динамически определить, какой класс преобладает, и может оптимизировать его.
Помните, что микробенчмарки в этой теме глубоко испорчены, поэтому возьмите их с небольшим количеством соли. Наименьший недостаток на сегодняшний день - у Питера Лоури: он выполняет прогрев прогона, чтобы соединить методы, и он (сознательно) побеждает анализ побега, чтобы убедиться, что распределение действительно происходит. Но даже у этого есть свои проблемы: например, можно ожидать, что огромное количество хранилищ массивов побеждает кеши и буферы хранилищ, так что в конечном итоге это будет в основном ориентиром для памяти, если ваши выделения очень быстрые. (Благодарность Питеру за то, что он сделал правильное заключение: разница составляет «150 нс», а не «2,5х». Я подозреваю, что он зарабатывает этим на жизнь.)
Интересно, что установка setAccessible (true), которая пропускает проверки безопасности, снижает стоимость на 20%.
Без setAccessible (true)
new A(), 70 ns
A.class.newInstance(), 214 ns
new A(), 84 ns
A.class.newInstance(), 229 ns
С setAccessible (true)
new A(), 69 ns
A.class.newInstance(), 159 ns
new A(), 85 ns
A.class.newInstance(), 171 ns
1000000
вызовов?
setAccessible()
может иметь гораздо больше различий в целом, особенно для методов с несколькими аргументами, поэтому он всегда должен вызываться.
Да, это значительно медленнее. Мы выполняли некоторый код, который сделал это, и хотя у меня пока нет доступных метрик, конечным результатом было то, что нам пришлось реорганизовать этот код, чтобы не использовать отражение. Если вы знаете, что это за класс, просто вызовите конструктор напрямую.
В doReflection () это накладные расходы из-за Class.forName («misc.A») (что потребует поиска класса, потенциально сканирующего путь к классу в файловой системе), а не newInstance (), вызываемого в классе. Мне интересно, как будет выглядеть статистика, если Class.forName ("misc.A") выполняется только один раз вне цикла for, это не обязательно должно выполняться для каждого вызова цикла.
Да, всегда будет медленнее создавать объект путем отражения, потому что JVM не может оптимизировать код во время компиляции. См. Учебники Sun / Java Reflection для получения более подробной информации.
Смотрите этот простой тест:
public class TestSpeed {
public static void main(String[] args) {
long startTime = System.nanoTime();
Object instance = new TestSpeed();
long endTime = System.nanoTime();
System.out.println(endTime - startTime + "ns");
startTime = System.nanoTime();
try {
Object reflectionInstance = Class.forName("TestSpeed").newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
endTime = System.nanoTime();
System.out.println(endTime - startTime + "ns");
}
}
Class.forName()
) от экземпляра (newInstance ()), потому что они значительно различаются по своим характеристикам производительности, и вы можете иногда избегать повторного поиска в хорошо спроектированной системе.
Часто вы можете использовать Apache Commons BeanUtils или PropertyUtils, которые самоанализ (в основном они кэшируют метаданные о классах, поэтому им не всегда нужно использовать отражение).
Я думаю, это зависит от того, насколько легким / тяжелым является целевой метод. если целевой метод очень легкий (например, метод получения / установки), он может быть в 1-3 раза медленнее. если целевой метод занимает около 1 миллисекунды или выше, производительность будет очень близка. Вот тест, который я сделал с Java 8 и refleasm :
public class ReflectionTest extends TestCase {
@Test
public void test_perf() {
Profiler.run(3, 100000, 3, "m_01 by refelct", () -> Reflection.on(X.class)._new().invoke("m_01")).printResult();
Profiler.run(3, 100000, 3, "m_01 direct call", () -> new X().m_01()).printResult();
Profiler.run(3, 100000, 3, "m_02 by refelct", () -> Reflection.on(X.class)._new().invoke("m_02")).printResult();
Profiler.run(3, 100000, 3, "m_02 direct call", () -> new X().m_02()).printResult();
Profiler.run(3, 100000, 3, "m_11 by refelct", () -> Reflection.on(X.class)._new().invoke("m_11")).printResult();
Profiler.run(3, 100000, 3, "m_11 direct call", () -> X.m_11()).printResult();
Profiler.run(3, 100000, 3, "m_12 by refelct", () -> Reflection.on(X.class)._new().invoke("m_12")).printResult();
Profiler.run(3, 100000, 3, "m_12 direct call", () -> X.m_12()).printResult();
}
public static class X {
public long m_01() {
return m_11();
}
public long m_02() {
return m_12();
}
public static long m_11() {
long sum = IntStream.range(0, 10).sum();
assertEquals(45, sum);
return sum;
}
public static long m_12() {
long sum = IntStream.range(0, 10000).sum();
assertEquals(49995000, sum);
return sum;
}
}
}
Полный тестовый код доступен на GitHub: ReflectionTest.java