Улучшение получения и настройки производительности с помощью ASM или Javassist - PullRequest
15 голосов
/ 11 июня 2010

Я бы хотел избежать отражения в проекте с открытым исходным кодом, который я разрабатываю.Здесь у меня есть классы, подобные следующему:

public class PurchaseOrder {

   @Property
   private Customer customer;

   @Property
   private String name;
}

Я сканирую аннотацию @Property, чтобы определить, что я могу задать и получить с помощью PurchaseOrder.Существует множество таких классов, использующих java.lang.reflect.Field.get() и java.lang.reflect.Field.set().

. В идеале я хотел бы создать для каждого свойства такой вызывающий объект, как приведенный ниже.класс Я могу создать статический внутренний класс PurchaseOrder примерно так.

static class customer_Field implements PropertyAccessor<PurchaseOrder, Customer> {
   public void set(PurchaseOrder order, Customer customer) {
      order.customer = customer;
   }  
   public Customer get(PurchaseOrder order) {
      return order.customer;
   }
}

С этим я полностью избегаю затрат на отражение.Теперь я могу установить и получить из моих экземпляров с собственной производительностью.Может кто-нибудь сказать мне, как я это сделаю.Пример кода был бы отличным.Я искал в сети хороший пример, но не могу найти ничего подобного.Примеры ASM и Javasist также довольно скудны.

Ключ в том, что у меня есть интерфейс, который я могу обойти.Поэтому у меня могут быть различные реализации, возможно, одна с Java Reflection по умолчанию, другая с ASM, а другая с Javassist?

Любая помощь будет принята с благодарностью.

Ответы [ 5 ]

10 голосов
/ 15 июня 2010

ASM

Используя ASMifierClassVisitor, вы можете точно увидеть, какой код нужно написать для генерации внутренних классов:

ASMifierClassVisitor.main(new String[] { PurchaseOrder.customer_Field.class
    .getName() });

Остальное простоопределить, какие биты вам нужно параметризировать в коде вашего генератора.Пример вывода для PurchaseOrder$customer_Field, который станет файлом inject/PurchaseOrder$customer_Field.class:

public static byte[] dump () throws Exception {

  ClassWriter cw = new ClassWriter(0);
  FieldVisitor fv;
  MethodVisitor mv;
  AnnotationVisitor av0;

  cw.visit(V1_6, ACC_SUPER, "inject/PurchaseOrder$customer_Field",
      "Ljava/lang/Object;"+
      "Linject/PropertyAccessor<Linject/PurchaseOrder;Linject/Customer;>;", 
      "java/lang/Object",
      new String[] { "inject/PropertyAccessor" });
//etc

(я использовал «inject» в качестве пакета.)

Вы будететакже необходимо создать искусственные средства доступа с использованием классов посетителей ASM:

{
mv = cw.visitMethod(ACC_STATIC + ACC_SYNTHETIC, "access$0", 
          "(Linject/PurchaseOrder;Linject/Customer;)V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, "inject/PurchaseOrder",
          "customer", "Linject/Customer;");
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_STATIC + ACC_SYNTHETIC, "access$1", 
          "(Linject/PurchaseOrder;)Linject/Customer;", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, "inject/PurchaseOrder", "
          customer", "Linject/Customer;");
mv.visitInsn(ARETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}

См. этот проект для примера того, как внедрить методы.Благодаря этому я полностью избегаю затрат на рефлексию.

Поскольку все это будет сделано во время выполнения:

  • , это предварительная стоимость этого анализа игенерация кода
  • вам нужно как-то обнаружить и проанализировать эти сгенерированные типы
4 голосов
/ 19 июня 2010

Пример использования Javassist, однако он требует, чтобы ваши свойства имели защиту на уровне пакета, а не были частными

public class AccessorGenerator {

    private final ClassPool pool;

    public PropertyGenerator() {
        pool = new ClassPool();
        pool.appendSystemPath();
    }

    public Map<String, PropertyAccessor> createAccessors(Class<?> klazz) throws Exception {
        Field[] fields = klazz.getDeclaredFields();

        Map<String, PropertyAccessor> temp = new HashMap<String, PropertyAccessor>();
        for (Field field : fields) {
            PropertyAccessor accessor = createAccessor(klazz, field);
            temp.put(field.getName(), accessor);
        }

        return Collections.unmodifiableMap(temp);
    }

    private PropertyAccessor createAccessor(Class<?> klazz, Field field) throws Exception {
        final String classTemplate = "%s_%s_accessor";
        final String getTemplate = "public Object get(Object source) { return ((%s)source).%s; }";
        final String setTemplate = "public void set(Object dest, Object value) { return ((%s)dest).%s = (%s) value; }";

        final String getMethod = String.format(getTemplate, 
                                               klazz.getName(),
                                               field.getName());
        final String setMethod = String.format(setTemplate, 
                                               klazz.getName(), 
                                               field.getName(), 
                                               field.getType().getName());

        final String className = String.format(classTemplate, klazz.getName(), field.getName());

        CtClass ctClass = pool.makeClass(className);
        ctClass.addMethod(CtNewMethod.make(getMethod, ctClass));
        ctClass.addMethod(CtNewMethod.make(setMethod, ctClass));
        ctClass.setInterfaces(new CtClass[] { pool.get(PropertyAccessor.class.getName()) });
        Class<?> generated = ctClass.toClass();
        return (PropertyAccessor) generated.newInstance();
    }

    public static void main(String[] args) throws Exception {
        AccessorGenerator generator = new AccessorGenerator();

        Map<String, PropertyAccessor> accessorsByName = generator.createAccessors(PurchaseOrder.class);

        PurchaseOrder purchaseOrder = new PurchaseOrder("foo", new Customer());

        accessorsByName.get("name").set(purchaseOrder, "bar");
        String name = (String) accessorsByName.get("name").get(purchaseOrder);
        System.out.println(name);
    }
}
1 голос
/ 11 июня 2010

Вы также можете использовать процессоры аннотаций, что позволяет избежать сложности манипулирования байт-кодом. (см. эту статью о javabeat )

0 голосов
/ 15 июня 2010

Цель - производительность!

Да, это очень часто цель.Но то, что вы делаете сейчас с PropertyAccessor, ваша производительность снижается!Каждый раз, когда вы хотите получить или установить свойство, вам нужно будет создать новый экземпляр для customer_Field.Или вы должны сохранить свой экземпляр.Я не вижу, в чем проблема с простым геттером или сеттером.

public class PurchaseOrder {

   @Property
   private Customer customer;

   @Property
   private String name;

   pulic void setCustomer(Customer c)
   {
       this.customer = c;
   }

   public Customer getCustomer()
   {
       return customer;
   }

   // The same for name
}

Это производительность!Нативный код может быть в 14 раз быстрее, но вам действительно нужно это быстро?Ява великолепна.Зачем?Из-за этого независимость платформы.И если вы собираетесь делать нативные вещи, мощь Java исчезнет.Итак, в чем разница между ожиданием одной минуты для выполнения всего, что нужно программам и ожиданием 50 секунд."Где мой в 14 раз быстрее?"Вам нужно не только получить и установить.Вам нужно что-то делать со всеми данными.

И я не думаю, что это будет быстрее, потому что вы просто получаете и устанавливаете экземпляры объектов и примитивы.Нативная Java создана для:

  • методов, которые должны вычислять что-то, что будет действительно быстрее в машинном коде, чем с Java Runtime Environment (множество java.lang.Math методов, таких как sqrt(). Они могутзапрограммируйте его на Java, но это будет медленнее)
  • вещей, которые Java не может сделать самостоятельно, например: выход из приложения, создание сокетов, запись / чтение файлов, запуск других процессов и т. д. Это не такчистая Java, которая является родным машинным кодом, который это делает.

Так что я надеюсь, что убедил вас, и вы сохраните его с помощью Java.

0 голосов
/ 12 июня 2010

Я удивлен, что отражение намного медленнее.Если вы прогреваете JVM, он должен быть не более чем в 5 раз медленнее, чем прямой доступ.КСТАТИ Микропроцессор может дать неверные результаты, потому что простой метод получения / установки может быть легко оптимизирован, если он не выполняет реальную работу.

Еще один способ избежать отражения и байт-кода - этоиспользуйте класс sun.misc.Unsafe.С ним нужно обращаться осторожно, и он не переносится на все JVM, но в 2-3 раза быстрее, чем отражение.См. Мой проектsence-rmi для примеров.

Другой вариант - генерировать код и компилировать его на лету.Вы можете использовать API компилятора или такую ​​библиотеку, как BeanShell.

Примечание: если у вас есть приватное поле, к нему нельзя получить доступ из другого класса с помощью байтового кода.Это ограничение JVM.Внутренние и вложенные классы избегают этого, генерируя методы доступа для вас, например, доступ к $ 100 в классе с закрытыми методами (вы, возможно, видели это в стеке вызовов). Однако это означает, что вы не можете добавить класс для доступа к закрытым полям без изменения оригиналакласс.

...