Odbicie Javy: dlaczego jest tak wolne?

Zawsze unikałem odbicia Javy w oparciu o jej reputację powolności. Osiągnąłem punkt w projektowaniu mojego obecnego projektu, w którym możliwość jego użycia uczyniłaby mój kod o wiele bardziej czytelny i elegancki, więc postanowiłem spróbować.

Byłem po prostu zdumiony różnicą, zauważyłem czasami prawie 100x dłuższe czasy biegu. Nawet w tym prostym przykładzie, gdzie po prostu tworzy instancję pustej klasy, jest to niewiarygodne.

class B {

}

public class Test {

    public static long timeDiff(long old) {
        return System.currentTimeMillis() - old;
    }

    public static void main(String args[]) throws Exception {

        long numTrials = (long) Math.pow(10, 7);

        long millis;

        millis = System.currentTimeMillis();

        for (int i=0; i<numTrials; i++) {
            new B();
        }
        System.out.println("Normal instaniation took: "
                 + timeDiff(millis) + "ms");

        millis = System.currentTimeMillis();

        Class<B> c = B.class;

        for (int i=0; i<numTrials; i++) {
            c.newInstance();
        }

        System.out.println("Reflecting instantiation took:" 
              + timeDiff(millis) + "ms");

    }
}
Tak naprawdę, moje pytania are
  • Dlaczego tak wolno? Czy jest coś, co robię źle? (nawet powyższy przykład pokazuje różnicę). Ciężko mi uwierzyć, że naprawdę może być 100x wolniej niż normalna instancja.

  • Czy jest jeszcze coś, co można lepiej wykorzystać do traktowania kodu jako danych (pamiętaj, że utknąłem na razie z Javą)

Author: Jonas, 2009-09-08

7 answers

Twój test może być błędny. Ogólnie rzecz biorąc, JVM może zoptymalizować normalną instancję, ale nie może dokonać optymalizacji dla odblaskowego przypadku użycia.

Dla tych, którzy zastanawiają się, jakie były czasy, dodałem fazę rozgrzewki i użyłem tablicy do utrzymywania utworzonych obiektów(bardziej podobnych do tego, co może zrobić prawdziwy program). Uruchomiłem kod testowy na moim systemie OSX, jdk7 i mam to:

Pobrano: 5180ms
Normalna instancja: 2001ms

Zmodyfikowany test:

public class Test {

    static class B {

    }

    public static long timeDiff(long old) {
        return System.nanoTime() - old;
    }

    public static void main(String args[]) throws Exception {

        int numTrials = 10000000;
        B[] bees = new B[numTrials];
        Class<B> c = B.class;
        for (int i = 0; i < numTrials; i++) {
            bees[i] = c.newInstance();
        }
        for (int i = 0; i < numTrials; i++) {
            bees[i] = new B();
        }

        long nanos;

        nanos = System.nanoTime();
        for (int i = 0; i < numTrials; i++) {
            bees[i] = c.newInstance();
        }
        System.out.println("Reflecting instantiation took:" + TimeUnit.NANOSECONDS.toMillis(timeDiff(nanos)) + "ms");

        nanos = System.nanoTime();
        for (int i = 0; i < numTrials; i++) {
            bees[i] = new B();
        }
        System.out.println("Normal instaniation took: " + TimeUnit.NANOSECONDS.toMillis(timeDiff(nanos)) + "ms");
    }


}
 38
Author: Tim Bender,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-03 18:24:11

Odbicie jest powolne z kilku oczywistych powodów:

  1. kompilator nie może w ogóle optymalizować, ponieważ nie może mieć rzeczywistego pojęcia o tym, co robisz. Prawdopodobnie dotyczy to również JIT
  2. wszystko co jest wywoływane / tworzone musi być odkryte (tj. klasy wyszukane po nazwie, metody wyszukane dla dopasowań itp.)
  3. argumenty muszą być ubrane poprzez Boks/unboxing, pakowanie w tablice, Exceptions owinięte w InvocationTargetException s i ponownie rzucone itp.
  4. Wszystkie przetwarzanie, które Jon Skeet wspomina tutaj .

To, że coś jest 100x wolniejsze nie oznacza, że jest zbyt wolne dla ciebie zakładając, że odbicie jest "właściwą drogą" do zaprojektowania programu. Na przykład wyobrażam sobie, że IDE mocno wykorzystuje refleksję, a moje IDE jest w większości OK z perspektywy wydajności.

Po tym wszystkim, overhead of reflection prawdopodobnieblednie w nieistotności gdy w porównaniu z , powiedzmy, parsowanie XML lub dostęp do bazy danych!

Kolejną kwestią, o której należy pamiętać, jest to, że mikro-benchmarki są notorycznie wadliwym mechanizmem określania, jak szybko coś jest w praktyce . Jak również Tim Bender 's uwagi , JVM wymaga czasu ,aby" rozgrzać się", JIT może ponownie zoptymalizować hotspoty kodu w locie itp.

 40
Author: oxbow_lakes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:32:17

Kod JITted do tworzenia instancji B jest niewiarygodnie lekki. Zasadniczo musi przydzielić wystarczającą ilość pamięci (co jest po prostu zwiększaniem wskaźnika, chyba że GC jest wymagane) i to wszystko - nie ma kodu konstruktora do wywołania naprawdę; Nie wiem, czy JIT pomija go, czy nie, ale tak czy inaczej nie ma wiele do zrobienia.

Porównaj to ze wszystkim, co odbicie musi zrobić:

  • Sprawdź, czy istnieje konstruktor bez parametru
  • Sprawdź dostępność konstruktora bez parametru
  • Sprawdź, czy rozmówca ma dostęp do korzystania z odbicia w ogóle
  • Oblicz (w czasie realizacji) ile miejsca trzeba przeznaczyć
  • wywołanie kodu konstruktora (ponieważ nie będzie wiedział wcześniej, że konstruktor jest pusty)

... i pewnie inne rzeczy, o których nawet nie pomyślałem.

Zazwyczaj odbicie nie jest używane w kontekście krytycznym dla wydajności; jeśli potrzebujesz takiego dynamicznego zachowania, możesz zamiast tego przydałoby się coś w rodzaju BCEL.

 24
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-08 06:56:37

Wygląda na to, że jeśli sprawisz, że konstruktor będzie dostępny, uruchomi się on znacznie szybciej. Teraz jest tylko około 10-20 razy wolniejszy niż w drugiej wersji.

    Constructor<B> c = B.class.getDeclaredConstructor();
    c.setAccessible(true);
    for (int i = 0; i < numTrials; i++) {
        c.newInstance();
    }

Normal instaniation took: 47ms
Reflecting instantiation took:718ms

A jeśli używasz maszyny wirtualnej serwera, jest ona w stanie ją bardziej zoptymalizować, tak że jest tylko 3-4 razy wolniejsza. To dość typowe wykonanie. Artykuł że Geo linked jest dobrą lekturą.

Normal instaniation took: 47ms
Reflecting instantiation took:140ms

Ale jeśli włączysz zastępowanie skalarne za pomocą-XX:+DoEscapeAnalysis, JVM będzie w stanie zoptymalizować zwykłą instancję daleko (będzie to 0-15ms), ale instancja refleksyjna pozostaje taka sama.

 9
Author: Esko Luontola,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-03 21:03:09
    [[1]}Reflection było bardzo powolne, kiedy po raz pierwszy wprowadzono, ale zostało znacznie przyspieszone w nowszych wersjach JRE [2]}
  • mimo to nie jest dobrym pomysłem używanie odbicia w wewnętrznej pętli
  • kod oparty na odbiciu ma niski potencjał optymalizacji opartej na JIT
  • Reflection jest najczęściej używany w łączeniu luźno sprzężonych komponentów, tj. w wyszukiwaniu konkretnych klas i metod, gdzie znane są tylko interfejsy: dependeny - injection Framework, instantiating JDBC implementation klasy lub parsery XML. Te zastosowania często można wykonać raz przy starcie systemu, więc mała nieefektywność i tak nie ma znaczenia!
 2
Author: mfx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-08 07:56:55

Może znajdziesz Ten artykuł ciekawy.

 2
Author: Geo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-03 19:24:22

Kod Tima Bendera daje te wyniki na mojej maszynie (jdk_1. 8_45, os_x 10.10, i7, 16G):

Reflecting instantiation took:1139ms Normal instaniation took: 4969ms

Wygląda na to, że na nowoczesnym JVM kod odbicia będzie również dobrze zoptymalizowany.

 0
Author: bob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-19 17:37:45