2013-10-24 4 views
45

Как мы знаем, отражение является гибким, но медленным методом поддержания и изменения поведения кода во время выполнения.Более быстрые альтернативы отражению Java

Но если нам нужно использовать такую ​​функциональность, существуют ли более быстрые методы программирования в Java по сравнению с Reflection API для динамических изменений? Каковы плюсы и минусы этих альтернатив против отражения?

+8

Основной альтернативой является _not_ манипулирование и изменение кода во время выполнения, что обычно довольно быстро. –

+4

Довольно сложной альтернативой было бы прямое управление байт-кодом, см. Https://code.google.com/p/java-box-factory/, http://asm.ow2.org/, http: //commons.apache .org/proper/commons-bcel/ – harpun

+3

Что вы делаете с отражением, и каково текущее влияние на производительность? –

ответ

99

Одна из альтернатив Reflection - генерировать файл класса динамически. Этот сгенерированный класс должен выполнять требуемое действие, например. вызывает метод, обнаруженный во время выполнения, и реализует interface, известный во время компиляции, так что с помощью этого интерфейса можно вызвать сгенерированный метод не отражающим образом. Есть один улов: если применимо, Reflection делает тот же трюк внутри. Это не работает в особых случаях, например. при вызове метода private, поскольку вы не можете создать файл юридического класса, вызывающий его. Таким образом, в реализации Reflection существуют разные типы обработчиков вызовов, используя либо сгенерированный код, либо собственный код. Вы не можете победить.

Но более важно то, что Reflection выполняет проверки безопасности при каждом вызове. Таким образом, ваш сгенерированный класс будет проверяться только при загрузке и создании экземпляров, что может стать большой победой. Но в качестве альтернативы вы можете вызвать setAccessible(true) на экземпляр Method, чтобы включить проверки безопасности. Тогда остается только незначительная потеря производительности автобоксинга и создания массива varargs.

С Java 7 есть альтернатива обоим, MethodHandle. Большим преимуществом является то, что, в отличие от двух других, он даже работает в среде с ограничениями безопасности. Проверки доступа для MethodHandle выполняются при его приобретении, но не при его вызове. Он имеет так называемую «полиморфную подпись», что означает, что вы можете ссылаться на нее с произвольными типами аргументов без автоматического бокса или создания массива. Конечно, неправильные типы аргументов создадут соответствующий RuntimeException.

(Обновления) С Java 8, есть возможность использовать обратный конец функции языка лямбда-выражение и эталонного метод во время выполнения. Этот бэкэнд делает именно то, что описано в начале, генерируя класс динамически, который реализует interface, который ваш код может вызывать непосредственно, когда он известен во время компиляции. Точная механика специфична для реализации, поэтому не определена, но вы можете предположить, что реализация попытается сделать так, чтобы сделать вызов как можно быстрее. Текущая реализация JRE от Oracle делает это отлично. Мало того, что это избавляет вас от бремени создания такого класса accessor, оно также способно делать то, что вы никогда не могли сделать, - вызывать даже private методы с помощью сгенерированного кода. Я обновил пример, чтобы включить это решение. В этом примере используется стандарт interface, который уже существует и имеет желаемую подпись метода. Если такого соответствия interface не существует, вам необходимо создать свой собственный интерфейс доступа с помощью метода с правильной подписью. Но, конечно, теперь для примера кода требуется Java 8 для запуска.

Вот простой тест пример:

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; 
    } 
} 

Th старая программа напечатана в моей установке Java 7: direct: 0,03s, mh: 0,32s, reflection: 1,05s, который предположил, что MethodHandle была хорошей альтернативой.Теперь обновленная программа, работающая под Java 8 на том же самом компьютере, напечатанном direct: 0,02s, lambda: 0,02s, mh: 0,35s, reflection: 0,40s, что наглядно показывает, что производительность отражения была улучшена до такой степени, что может оказаться ненужным, если вы не используете ее для выполнения лямбда-трюка, что явно превосходит все отражающие альтернативы, что не вызывает удивления, поскольку это просто прямой вызов (ну, почти: один уровень косвенности). Обратите внимание, что я сделал целевой метод private, чтобы продемонстрировать возможность эффективного вызова методов private.

Как всегда, я должен указать на простоту этого теста и как он искусственен. Но я думаю, что эта тенденция отчетливо видна и даже более важна, результаты убедительно объяснимы.

+3

+1 для предоставленного сравнения – maasg

+0

Надеюсь, обработчики методов ускорятся с Java 8. – Ingo

+2

С текущей бета-версией это не так. Но, ну, это 'бета'. И они будут использоваться реализацией лямбда, поэтому есть еще большее усилие для ее улучшения. – Holger

7

В качестве альтернативы для отражения используется интерфейс. Просто извлекая из Effective Java by Joshua Bloch.

Мы можем получить много преимуществ отражения в то время подвергаясь несколько своих затрат, используя его только в очень ограниченной форме. Для многих программ , которые должны использовать класс, который недоступен во время компиляции, во время компиляции существует соответствующий интерфейс или суперкласс от , который относится к классу. Если это так, вы можете создать объективы и получить доступ к ним обычно через их интерфейс или суперкласс. Если соответствующий конструктор не имеет параметров, то вам даже не нужно использовать java.lang.reflect; метод Class.newInstance обеспечивает требуемую функциональность.

Использование отражения только для создания объекта, т.е.

// 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); 
} 

Хотя эта программа просто игрушка, техника демонстрирует это очень мощный. Игрушечную программу можно было легко превратить в общий тестер , который проверяет установленную реализацию Set на , агрессивно манипулируя одним или несколькими экземплярами и проверяя, что они подчиняются контракту Set. Аналогичным образом, он может быть превращен в общий набор инструментов анализа производительности. Фактически, этот метод достаточно , чтобы реализовать полномасштабную инфраструктуру поставщика услуг. Большинство того времени, эта техника - все, что вам нужно на пути отражение.

Этот пример демонстрирует два недостатка отражения. Во-первых, пример может генерировать три ошибки времени выполнения, все из которых были бы ошибками во время компиляции, если рефлексивная копия не использовалась. Во-вторых, для создания экземпляра класса от его имени требуется двадцать строк нужного кода, тогда как вызов конструктора будет аккуратно подогнать по одной строке. Однако этими недостатками являются , ограниченный частью программы, которая создает экземпляр объекта. После создания экземпляра он неотличим от любого другого экземпляра Set .

+0

-При голосования прокомментировать. – Trying

11

Я создал небольшую библиотеку под названием lambda-factory. Он основан на LambdaMetafactory, но избавляет вас от необходимости находить или создавать интерфейс, соответствующий методу.

Вот некоторые примеры для среды выполнения итераций 10E8 (воспроизводимое с классом PerformanceTest):

Lambda: 0.02s, прямые: 0,01 с, Отражение: 4.64s для метода (междунар, Int)
Lambda: 0,03 s, прямой: 0.02s, Отражение: 3.23s для метода (Object, Int)

Допустим, у нас есть класс, называемый MyClass, который определяет следующие методы:

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

мы можем получить доступ к этим метамфетамин в.п.с. как это:

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! 

Обратите внимание, что при вызове лямбды, вы должны выбрать метод вызова, который содержит целевой тип возвращаемого значения в его названии. - варгары и авто бокс были слишком дорогими.

В приведенном выше примере выбранный метод invoke_for_float указывает, что мы вызываем метод, который возвращает float. Если метод, к которому вы пытаетесь получить доступ, возвращает fx a String, примитив в штучной упаковке (Integer, Boolean и т. Д.) Или какой-либо пользовательский объект, вы должны позвонить invoke_for_Object.

Проект хороший шаблон для экспериментов с LambdaMetafactory, так как он содержит рабочий код для различных аспектов:

  1. статических вызовов и например вызовов
  2. Доступа к частным методам и методы из других пакетов
  3. логика «invokeSpecial», т. е. когда созданная реализация такова, что она обходит динамическую отправку методов.
+0

Проверял ваш код, не в состоянии скомпилировать код, Lamba - не может быть разрешен к типу. –

+0

@aryanRaj_kary Это отлично работает для меня. На вопрос, на который ссылается проект github, вы ответите в соответствии с параграфом «Создание из источника». Краткое описание: Вы должны A) запустить «mvn clean install» и B) Поручить вашей среде IDE сгенерировать исходный файл. Альтернативно и намного проще вы просто загрузите файл jar и используйте его (щелкните на вкладке «релизы») – Hervian

+0

Пробовал банку, но получал это исключение: java.lang.NoSuchFieldException: allowedModes Метод метода = TestSACP.class.getDeclaredMethod («getData», String.class); Лямбда лямбда = LambdaFactory.create (метод); Строковый результат = (String) lambda.invoke_for_Object (новый TestSACP(), «привет»); открытый класс TestSACP { private String getData (String a) { \t \t возвращение «от SACP» + a; \t} } –

Смежные вопросы