Відповіді:
Так - абсолютно. Пошук класу за допомогою відображення за величиною дорожче.
Цитування документації 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 для DRY)
Я клянусь, якби хтось підійшов до мене на роботі і запитав у мене це, я б дуже насторожився над їх кодом протягом наступних кількох місяців.
Ніколи не слід оптимізувати, поки ви впевнені, що вам це потрібно, до цього часу просто напишіть хороший читабельний код.
О, і я не маю на увазі також написати дурний код. Подумайте лише про найчистіший спосіб, яким ви це можете зробити - без копіювання та вставки тощо (все ж будьте обережні до таких речей, як внутрішні петлі та використання колекції, що найкраще відповідає вашим потребам. Ігнорування цих не "неоптимізованих" програмувань , це "погано" програмування)
Мене це лякає, коли я чую подібні запитання, але тоді я забуваю, що всі повинні самі вивчити всі правила, перш ніж вони дійсно їх отримають. Ви отримаєте це після того, як витратили місячний місяць на налагодження чогось, кого хтось "оптимізував".
Редагувати:
Цікава річ трапилась у цій темі. Перевірте відповідь №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% впевнений (і зараз мені не хочеться читати джерело), але я думаю, що Гійс генерує байт-код, щоб зробити ін'єкцію залежності. Виправте мене, якщо я помиляюся.
"Значуще" повністю залежить від контексту.
Якщо ви використовуєте відображення для створення єдиного оброблювального об'єкта на основі деякого файлу конфігурації, а потім витрачаєте решту часу на виконання запитів до бази даних, то це несуттєво. Якщо ви створюєте велику кількість об'єктів за допомогою відображення в тісному циклі, то так, це важливо.
Взагалі, гнучкість дизайну (де це потрібно!) Повинна керувати вашим використанням відображення, а не продуктивності. Однак, щоб визначити, чи є проблема в роботі, потрібно проаналізувати, а не отримати довільні відповіді з дискусійного форуму.
Існує деяка накладні витрати з відображенням, але вона набагато менша, ніж у сучасних ВМ, ніж раніше.
Якщо ви використовуєте роздуми для створення кожного простого об'єкта у вашій програмі, то щось не так. Використовувати його час від часу, коли у вас є вагомі причини, взагалі не повинно бути проблемою.
Так, при використанні 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 міг визначити, який клас інстанціюється. Якщо ідентифікацію класу неможливо визначити, код виділення не може бути вписаний. Гірше, аналіз втечі не вдається, і об'єкт не може бути виділений стеком. Якщо вам пощастить, профілювання часу запуску JVM може прийти на допомогу, якщо цей код нагріється, і може динамічно визначити, який клас переважає і може оптимізувати цей клас.
Будьте в курсі, що мікробензикові позначки в цій нитці глибоко хибні, тому приймайте їх із зерном солі. Найменш недоліком на сьогодні є Пітер Лоурі: він виконує розминку для отримання методів, і він (свідомо) перемагає аналіз втечі, щоб переконатися, що розподіли дійсно відбуваються. Навіть у цього є свої проблеми: наприклад, можна очікувати, що величезна кількість магазинів масивів переможе кеші та буфери для зберігання, тому це може стати головним орієнтиром пам’яті, якщо ваш розподіл буде дуже швидким. (Кудо Петеру, як правильно зробити висновок: що різниця "150сн", а не "2,5х". Я підозрюю, що він робить подібні речі на життя.)
Цікаво, що встановлення setAccessible (true), яке пропускає перевірки безпеки, зменшує вартість на 20%.
Без setAccessible (вірно)
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-loop, насправді це не потрібно робити для кожного виклику циклу.
Так, завжди буде повільніше створювати об’єкт шляхом відображення, оскільки JVM не може оптимізувати код під час компіляції. Детальнішу інформацію див. У навчальних посібниках із відображенням Sun / Java .
Дивіться цей простий тест:
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 ()), оскільки вони суттєво відрізняються за своїми експлуатаційними характеристиками, і ви можете час від часу уникати повторного пошуку в добре розробленій системі.
Думаю, це залежить від того, наскільки легкий / важкий цільовий метод. якщо цільовий метод дуже легкий (наприклад, геттер / сетер), він може бути в 1-3 рази повільніше. якщо цільовий метод займає близько 1 мілісекунди або вище, то продуктивність буде дуже близькою. ось тест, який я зробив з Java 8 та Reflectasm :
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