Szybsze alternatywy dla odbicia Javy [zamknięty]

zamknięte. to pytanie nie spełnia wytycznych dotyczących przepełnienia stosu . Obecnie nie przyjmuje odpowiedzi.

chcesz poprawić to pytanie? Update the pytanie więc to on-topic {[3] } dla przepełnienia stosu.

Zamknięte 4 lata temu .

Popraw to pytanie

Jak wiemy, reflection jest elastyczną, ale powolną metodą utrzymywania i modyfikowania zachowania kodu w czasie wykonywania.

Ale jeśli mamy korzystać z takiej funkcjonalności, to czy są jakieś szybsze techniki programowania w Javie w porównaniu do API Reflection dla dynamicznych modyfikacji? Jakie są plusy i minusy tych alternatyw wobec refleksji?

Author: Muhd, 2013-10-24

3 answers

Alternatywą dla Reflection jest dynamiczne generowanie pliku klasy. Ta wygenerowana klasa powinna wykonać żądaną akcję, np. wywołuje metodę odkrytą w czasie wykonywania i implementuje interface znaną w czasie kompilacji, tak aby możliwe było wywołanie Wygenerowanej metody w sposób nieodblaskowy za pomocą tego interfejsu. Jest jeden haczyk: jeśli dotyczy, odbicie robi tę samą sztuczkę wewnętrznie. Nie działa to w szczególnych przypadkach, np. podczas wywoływania metody private, ponieważ nie można wygenerować akt prawny. Tak więc w implementacji Reflection istnieją różne typy obsługi wywołań, wykorzystujące wygenerowany kod lub kod natywny. Nie pokonasz tego.

[18]}ale ważniejsze jest to, że Reflection sprawdza bezpieczeństwo każdej inwokacji. Tak więc wygenerowana klasa będzie sprawdzana tylko przy ładowaniu i tworzeniu instancji, co może być dużą wygraną. Alternatywnie można wywołać setAccessible(true) Na instancji Method, aby wyłączyć kontrole bezpieczeństwa. Wtedy tylko niewielka utrata wydajności z autoboxingu i tworzenia macierzy varargs pozostaje.

Od Java 7 {[24] } istnieje alternatywa dla obu, MethodHandle. Dużą zaletą jest to, że w przeciwieństwie do pozostałych dwóch, działa nawet w środowiskach o ograniczonym dostępie. Kontrola dostępu dla MethodHandle jest wykonywana podczas jej pozyskiwania, ale nie podczas jej wywoływania. Posiada tzw. "sygnaturę polimorficzną", co oznacza, że można ją wywoływać dowolnymi typami argumentów bez auto-boksu ani tworzenia tablicy. Oczywiście błędne typy argumentów stworzy odpowiednią RuntimeException.

(Aktualizacja ) Z Java 8 istnieje możliwość użycia zaplecza wyrażenia lambda i funkcji języka referencyjnego metody w czasie wykonywania. Ten backend robi dokładnie to, co opisano na początku, generując klasę dynamicznie implementującą interface Twój kod może wywołać bezpośrednio, gdy jest znany w czasie kompilacji. Dokładna mechanika jest specyficzna dla implementacji, stąd niezdefiniowana, ale można założyć, że implementacja postaramy się, aby zaproszenie tak szybko, jak to możliwe. Obecna implementacja JRE firmy Oracle robi to doskonale. Nie tylko, że pozwala to zaoszczędzić od ciężaru generowania takiej klasy accessora, ale jest również w stanie zrobić to, czego nigdy nie mogłeś zrobić-wywołać nawet private metody za pomocą wygenerowanego kodu. Zaktualizowałem przykład, aby uwzględnić to rozwiązanie. Ten przykład używa standardu interface, który już istnieje i ma pożądany podpis metody. If no such matching interface istnieje, musisz stworzyć własny interfejs funkcjonalny accessora z metodą z odpowiednim podpisem. Ale, oczywiście, teraz przykładowy kod wymaga Java 8 do uruchomienia.

Oto prosty przykład benchmarka:
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.function.IntBinaryOperator;

public class TestMethodPerf
{
  private static final int ITERATIONS = 50_000_000;
  private static final int WARM_UP = 10;

  public static void main(String... args) throws Throwable
  {
 // hold result to prevent too much optimizations
    final int[] dummy=new int[4];

    Method reflected=TestMethodPerf.class
      .getDeclaredMethod("myMethod", int.class, int.class);
    final MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle mh=lookup.unreflect(reflected);
    IntBinaryOperator lambda=(IntBinaryOperator)LambdaMetafactory.metafactory(
      lookup, "applyAsInt", MethodType.methodType(IntBinaryOperator.class),
      mh.type(), mh, mh.type()).getTarget().invokeExact();

    for(int i=0; i<WARM_UP; i++)
    {
      dummy[0]+=testDirect(dummy[0]);
      dummy[1]+=testLambda(dummy[1], lambda);
      dummy[2]+=testMH(dummy[1], mh);
      dummy[3]+=testReflection(dummy[2], reflected);
    }
    long t0=System.nanoTime();
    dummy[0]+=testDirect(dummy[0]);
    long t1=System.nanoTime();
    dummy[1]+=testLambda(dummy[1], lambda);
    long t2=System.nanoTime();
    dummy[2]+=testMH(dummy[1], mh);
    long t3=System.nanoTime();
    dummy[3]+=testReflection(dummy[2], reflected);
    long t4=System.nanoTime();
    System.out.printf("direct: %.2fs, lambda: %.2fs, mh: %.2fs, reflection: %.2fs%n",
      (t1-t0)*1e-9, (t2-t1)*1e-9, (t3-t2)*1e-9, (t4-t3)*1e-9);

    // do something with the results
    if(dummy[0]!=dummy[1] || dummy[0]!=dummy[2] || dummy[0]!=dummy[3])
      throw new AssertionError();
  }

  private static int testMH(int v, MethodHandle mh) throws Throwable
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=(int)mh.invokeExact(1000, v);
    return v;
  }

  private static int testReflection(int v, Method mh) throws Throwable
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=(int)mh.invoke(null, 1000, v);
    return v;
  }

  private static int testDirect(int v)
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=myMethod(1000, v);
    return v;
  }

  private static int testLambda(int v, IntBinaryOperator accessor)
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=accessor.applyAsInt(1000, v);
    return v;
  }

  private static int myMethod(int a, int b)
  {
    return a<b? a: b;
  }
}

Stary program wydrukowany w mojej konfiguracji Java 7: direct: 0,03s, mh: 0,32s, reflection: 1,05s, który sugerował, że MethodHandle jest dobrą alternatywą. Teraz zaktualizowany program działający pod Javą 8 na tym samym komputerze wydrukował direct: 0,02s, lambda: 0,02s, mh: 0,35s, reflection: 0,40s, co wyraźnie pokazuje, że wydajność odbicia została poprawiona do pewnego stopnia to może sprawić, że radzenie sobie z MethodHandle będzie zbędne, chyba że użyjesz go do triku lambda, który wyraźnie przewyższa wszystkie odblaskowe alternatywy, co nie jest zaskoczeniem, ponieważ jest to tylko bezpośrednie połączenie (no, prawie: jeden poziom indrection). Zauważ, że stworzyłem metodę docelową private, aby zademonstrować możliwość efektywnego wywoływania metod even private.

Jak zawsze, muszę zwrócić uwagę na prostotę tego benchmarka i na to, jak sztuczny jest ten benchmark. Ale myślę, że tendencja jest wyraźnie widoczne i co ważniejsze, wyniki są przekonująco wyjaśnione.

 131
Author: Holger,
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
2019-11-25 17:55:00

Stworzyłem małą bibliotekę o nazwie lambda-factory . Jest on oparty na LambdaMetafactory, ale oszczędza kłopotów ze znalezieniem lub utworzeniem interfejsu, który pasuje do metody.

Oto przykładowe czasy uruchamiania dla iteracji 10e8 (odtwarzalne z klasą PerformanceTest):

Lambda: 0.02 s, Direct: 0.01 s, Reflection: 4.64 s dla metody(int, int)
Lambda: 0.03 s, Direct: 0.02 s, Reflection: 3.23 s dla method(Object, int)

Powiedzmy, że mamy klasę wywołana MyClass, która definiuje następujące metody:

private static String myStaticMethod(int a, Integer b){ /*some logic*/ }
private float myInstanceMethod(String a, Boolean b){ /*some logic*/ }

Możemy uzyskać dostęp do tych metod w następujący sposób:

Method method = MyClass.class.getDeclaredMethod("myStaticMethod", int.class, Integer.class); //Regular reflection call
Lambda lambda = LambdaFactory.create(method);  
String result = (String) lambda.invoke_for_Object(1000, (Integer) 565); //Don't rely on auto boxing of arguments!

Method method = MyClass.class.getDeclaredMethod("myInstanceMethod", String.class, Boolean.class);
Lambda lambda = LambdaFactory.create(method);
float result = lambda.invoke_for_float(new MyClass(), "Hello", (Boolean) null);  //No need to cast primitive results!

Zauważ, że podczas wywoływania lambda, musisz wybrać metodę wywołania, która zawiera typ zwracanej metody docelowej w jej nazwie. - varargs i auto boxing były zbyt drogie.

W powyższym przykładzie wybrana metoda invoke_for_float wskazuje, że wywołujemy metodę, która zwraca float. Jeśli metoda, do której próbujesz uzyskać dostęp, zwraca Fx a String, a boxed prymitywny (Integer, Boolean itp.) lub jakiś niestandardowy obiekt, można wywołać invoke_for_Object.

Projekt jest dobrym szablonem do eksperymentowania z LambdaMetafactory, ponieważ zawiera kod roboczy dla różnych aspektów:

  1. wywołania statyczne i wywołania instancji
  2. dostęp do prywatnych metod i metod z innych pakietów
  3. logika'invokeSpecial', tzn. gdzie wytworzona implementacja jest taka, że omija dynamiczną wysyłkę metod.
 16
Author: Hervian,
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
2016-05-23 20:35:57

Alternatywą dla odbicia jest użycie interfejsu. Po prostu biorąc z efektywnej Javy Joshua Bloch.

Możemy uzyskać wiele korzyści płynących z refleksji, ponosząc przy tym niewiele jego kosztów poprzez wykorzystanie go tylko w bardzo ograniczonej formie. Dla wielu programy, które muszą używać klasy niedostępnej w czasie kompilacji, istnieje w czasie kompilacji odpowiedni interfejs lub superklasa przez które odnoszą się do klasy. W takim przypadku można utworzyć instancje refleksyjnie i uzyskać do nich dostęp normalnie poprzez ich interfejs lub superklasa. Jeśli odpowiedni konstruktor nie ma żadnych parametrów, to nie trzeba nawet używać Javy.lang.reflect; Klasa.metoda newInstance zapewnia wymaganą funkcjonalność.

Użyj reflection tylko do tworzenia obiektu tzn.

// Reflective instantiation with interface access
   public static void main(String[] args) {
       // Translate the class name into a Class object
       Class<?> cl = null;
       try {
           cl = Class.forName(args[0]);
       } catch(ClassNotFoundException e) {
           System.err.println("Class not found.");
           System.exit(1);
       }
       // Instantiate the class
       Set<String> s = null;
       try {
           s = (Set<String>) cl.newInstance();
       } catch(IllegalAccessException e) {
           System.err.println("Class not accessible.");
           System.exit(1);
       } catch(InstantiationException e) {
           System.err.println("Class not instantiable.");
           System.exit(1);
       }
       // Exercise the set
       s.addAll(Arrays.asList(args).subList(1, args.length));
       System.out.println(s);
}

Podczas gdy ten program jest tylko zabawką, technika, którą demonstruje, jest bardzo potężne. Program zabawek można łatwo przekształcić w ogólny set tester, który waliduje określoną implementację zestawu przez agresywnie manipulować jedną lub kilkoma instancjami i sprawdzać, czy przestrzegaj ustalonego kontraktu. Podobnie, można go przekształcić w Rodzajowy Ustaw narzędzie do analizy wydajności. W rzeczywistości technika jest wystarczająco potężny do wdrożenia kompleksowej struktury dostawcy usług. Najbardziej czasu, ta technika jest wszystkim, czego potrzebujesz w drodze odbicie.

Ten przykład pokazuje dwie wady refleksji. Po pierwsze, przykład może wygenerować trzy błędy runtime, z których wszystkie miałyby były błędy w czasie kompilacji, jeśli nie zostały użyte instancje refleksyjne. Po drugie, potrzeba dwudziestu linijek żmudnego kodu, aby wygenerować instancję klasy od jej nazwy, natomiast wywołanie con-structor byłoby dopasuj starannie na jednej linii. Wady te są jednak, ograniczone do części programu, która tworzy instancję obiektu. Po utworzeniu instancji jest nie do odróżnienia od innych zestawów przykład.

 7
Author: Trying,
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-10-24 10:24:09