Odpowiedzi:
Tak - absolutnie. Patrzenie w górę klasy poprzez refleksję jest według wielkości droższe.
Cytując dokumentację Java na temat refleksji :
Ponieważ odbicie obejmuje typy, które są rozwiązywane dynamicznie, nie można przeprowadzić niektórych optymalizacji maszyn wirtualnych Java. W rezultacie operacje odblaskowe mają wolniejszą wydajność niż ich nieodblaskowe odpowiedniki i należy ich unikać w sekcjach kodu, które są często wywoływane w aplikacjach wrażliwych na wydajność.
Oto prosty test, który zhakowałem w ciągu 5 minut na moim komputerze, używając 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);
}
}
Z tymi wynikami:
35 // no reflection
465 // using reflection
Należy pamiętać, że wyszukiwanie i tworzenie instancji są wykonywane razem, aw niektórych przypadkach można je ponownie zmienić, ale to tylko podstawowy przykład.
Nawet jeśli tylko utworzysz instancję, nadal otrzymujesz hit wydajnościowy:
30 // no reflection
47 // reflection using one lookup, only instantiating
Ponownie YMMV.
Tak, jest wolniejszy.
Ale pamiętajcie o cholernej zasadzie nr 1 - WCZESNA OPTYMALIZACJA JEST KORZENIĄ CAŁEGO ZŁA
(Cóż, może być powiązany z numerem 1 dla DRY)
Przysięgam, gdyby ktoś podszedł do mnie w pracy i zapytał o to, będę bardzo czuwał nad jego kodem przez kilka następnych miesięcy.
Nie możesz nigdy optymalizować, dopóki nie jesteś pewien, że jej potrzebujesz, do tego czasu po prostu napisz dobry, czytelny kod.
Aha, i nie mam na myśli pisania głupiego kodu. Pomyśl tylko o najczystszym sposobie, w jaki możesz to zrobić - bez kopiowania i wklejania itp. (Nadal uważaj na rzeczy takie jak wewnętrzne pętle i korzystaj z kolekcji, która najlepiej pasuje do twoich potrzeb - ignorowanie tego nie jest "niezoptymalizowanym" programowaniem , to „złe” programowanie)
Strasznie mnie przeraża, kiedy słyszę takie pytania, ale potem zapominam, że każdy musi nauczyć się wszystkich zasad, zanim naprawdę je zrozumieją. Otrzymasz go po spędzeniu miesiąca osobowego na debugowaniu czegoś „zoptymalizowanego”.
EDYTOWAĆ:
W tym wątku wydarzyła się ciekawa rzecz. Sprawdź odpowiedź nr 1, to przykład tego, jak potężny jest kompilator w optymalizacji rzeczy. Test jest całkowicie nieważny, ponieważ nieodblaskowa instancja może zostać całkowicie uwzględniona.
Lekcja? NIGDY nie optymalizuj, dopóki nie napiszesz czystego, starannie zakodowanego rozwiązania i nie udowodnisz, że jest zbyt wolne.
Może się okazać, że A a = new A () jest optymalizowane przez JVM. Jeśli umieścisz obiekty w tablicy, nie będą one działać tak dobrze. ;) Poniższe wydruki ...
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);
}
}
To sugeruje, że różnica na moim komputerze wynosi około 150 ns.
Class.getDeclaredMethod
), a potem zadzwonię Method.invoke
wiele razy? Czy używam odbicia raz, czy tyle razy, ile go przywołuję? Pytanie uzupełniające, co jeśli zamiast Method
tego jest a, Constructor
a ja robię Constructor.newInstance
wiele razy?
Jeśli naprawdę istnieje potrzeba czegoś szybszego niż odbicie i nie jest to tylko przedwczesna optymalizacja, to generowanie kodu bajtowego z ASM to opcją jest lub biblioteką wyższego poziomu. Generowanie kodu bajtowego za pierwszym razem jest wolniejsze niż zwykłe użycie odbicia, ale po wygenerowaniu kodu bajtowego jest tak szybkie, jak normalny kod Java i zostanie zoptymalizowany przez kompilator JIT.
Kilka przykładów aplikacji wykorzystujących generowanie kodu:
Wywoływanie metod na serwerach proxy generowanych przez CGLIB jest nieco szybsze niż dynamiczne serwery proxy Javy , ponieważ CGLIB generuje kod bajtowy dla swoich serwerów proxy, ale dynamiczne proxy używają tylko odbicia ( zmierzyłem, że CGLIB jest około 10 razy szybszy w wywołaniach metod, ale tworzenie proxy było wolniejsze).
JSerial generuje kod bajtowy do odczytu / zapisu pól obiektów serializowanych, zamiast używać odbicia. Na stronie JSerial jest kilka testów porównawczych .
Nie jestem pewien w 100% (i nie mam teraz ochoty czytać źródła), ale myślę, że Guice generuje kod bajtowy do wstrzykiwania zależności. Popraw mnie, jeśli się mylę.
„Znaczące” jest całkowicie zależne od kontekstu.
Jeśli używasz odbicia do tworzenia pojedynczego obiektu obsługi na podstawie jakiegoś pliku konfiguracyjnego, a następnie spędzasz resztę czasu na wykonywaniu zapytań do bazy danych, to jest to nieistotne. Jeśli tworzysz dużą liczbę obiektów poprzez odbicie w ciasnej pętli, to tak, to jest ważne.
Ogólnie rzecz biorąc, elastyczność projektowania (w razie potrzeby!) Powinna wpływać na refleksję, a nie wydajność. Jednak aby określić, czy wydajność jest problemem, należy raczej profilować, niż uzyskiwać arbitralne odpowiedzi z forum dyskusyjnego.
Istnieje trochę narzutów z odbiciem, ale na nowoczesnych maszynach wirtualnych jest on znacznie mniejszy niż kiedyś.
Jeśli używasz odbicia do tworzenia każdego prostego obiektu w swoim programie, coś jest nie tak. Używanie go okazjonalnie, gdy masz dobry powód, nie powinno w ogóle stanowić problemu.
Tak, podczas korzystania z odbicia występuje spadek wydajności, ale możliwym obejściem optymalizacji jest buforowanie metody:
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");
spowoduje:
[java] Odruchowe wywołanie metody 1000000 razy z wyszukiwaniem zajęło 5618 milisów
[java] Odruchowe wywołanie metody 1000000 razy z pamięcią podręczną zajęło 270 milisekund
Odbicie jest powolne, chociaż alokacja obiektów nie jest tak beznadziejna, jak inne aspekty refleksji. Osiągnięcie równoważnej wydajności z tworzeniem instancji opartej na odbiciu wymaga napisania kodu, aby jit mógł określić, która klasa jest tworzona. Jeśli nie można określić tożsamości klasy, nie można wstawić kodu alokacji. Co gorsza, analiza ucieczki kończy się niepowodzeniem, a obiektu nie można zaalokować na stosie. Jeśli masz szczęście, profilowanie w czasie wykonywania maszyny JVM może przyjść na ratunek, jeśli ten kod stanie się gorący i może dynamicznie określić, która klasa dominuje i może zostać zoptymalizowana pod kątem tej.
Należy pamiętać, że mikrozlamy w tym wątku są głęboko wadliwe, więc weź je z przymrużeniem oka. Najmniej wadliwy jest zdecydowanie Peter Lawrey: wykonuje rozgrzewki, aby zrzucić metody, i (świadomie) pokonuje analizę ucieczki, aby upewnić się, że alokacje faktycznie mają miejsce. Nawet to ma jednak swoje problemy: na przykład można oczekiwać, że ogromna liczba magazynów macierzy pokona pamięci podręczne i bufory przechowywania, więc będzie to głównie test porównawczy pamięci, jeśli alokacje są bardzo szybkie. (Chwała Peterowi za prawidłowe wyciągnięcie wniosku: różnica wynosi „150 ns”, a nie „2,5x”. Podejrzewam, że zajmuje się takimi rzeczami na życie).
Co ciekawe, ustawienie setAccessible (true), które pomija kontrole bezpieczeństwa, daje 20% redukcję kosztów.
Bez setAccessible (true)
new A(), 70 ns
A.class.newInstance(), 214 ns
new A(), 84 ns
A.class.newInstance(), 229 ns
Z setAccessible (true)
new A(), 69 ns
A.class.newInstance(), 159 ns
new A(), 85 ns
A.class.newInstance(), 171 ns
1000000
wywołań?
setAccessible()
może mieć znacznie większą różnicę w ogóle, szczególnie w przypadku metod z wieloma argumentami, więc zawsze należy ją wywołać.
Tak, jest znacznie wolniejszy. Uruchomiliśmy kod, który to zrobił i chociaż w tej chwili nie mam dostępnych metryk, efekt końcowy był taki, że musieliśmy refaktoryzować ten kod, aby nie używać odbicia. Jeśli wiesz, czym jest klasa, po prostu zadzwoń bezpośrednio do konstruktora.
W doReflection () jest narzut spowodowany Class.forName („misc.A”) (który wymagałby przeszukania klasy, potencjalnie skanując ścieżkę klasy w systemie plików), a nie newInstance () wywołanej na klasie. Zastanawiam się, jak wyglądałyby statystyki, gdyby Class.forName ("misc.A") było wykonywane tylko raz poza pętlą for, tak naprawdę nie trzeba tego robić przy każdym wywołaniu pętli.
Tak, zawsze wolniej będzie tworzyć obiekt przez odbicie, ponieważ JVM nie może zoptymalizować kodu w czasie kompilacji. Więcej informacji można znaleźć w samouczkach dotyczących refleksji Sun / Java .
Zobacz ten prosty test:
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()
metodę lookup ( ) od instancjacji (newInstance ()), ponieważ różnią się one znacznie w charakterystyce wydajności i czasami możesz uniknąć wielokrotnego wyszukiwania w dobrze zaprojektowanym systemie.
Często możesz użyć Apache commons BeanUtils lub PropertyUtils, które przeprowadzają introspekcję (w zasadzie buforują metadane o klasach, więc nie zawsze muszą używać odbicia).
Myślę, że zależy to od tego, jak lekka / ciężka jest metoda docelowa. jeśli metoda docelowa jest bardzo lekka (np. getter / setter), może być 1 ~ 3 razy wolniejsza. jeśli metoda docelowa zajmie około 1 milisekundy lub więcej, wydajność będzie bardzo zbliżona. oto test, który przeprowadziłem z Javą 8 i reflektazmem :
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;
}
}
}
Pełny kod testu jest dostępny na GitHub: ReflectionTest.java