开发者

Improving field get and set performance with ASM or Javassist

I would like to avoid reflection in an open source project I am developing. Here I have classes like the following.

public class PurchaseOrder {

   @Property
   private Customer customer;

   @Property
   private开发者_如何学运维 String name;
}

I scan for the @Property annotation to determine what I can set and get from the PurchaseOrder reflectively. There are many such classes all using java.lang.reflect.Field.get() and java.lang.reflect.Field.set().

Ideally I would like to generate for each property an invoker like the following.

public interface PropertyAccessor<S, V> {
   public void set(S source, V value);
   public V get(S source);
}

Now when I scan the class I can create a static inner class of PurchaseOrder like so.

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

With these I totally avoid the cost of reflection. I can now set and get from my instances with native performance. Can anyone tell me how I would do this. A code example would be great. I have searched the net for a good example but can find nothing like this. The ASM and Javasist examples are pretty poor also.

The key here is that I have an interface that I can pass around. So I can have various implementations, perhaps one with Java Reflection as a default, one with ASM, and one with Javassist?

Any help would be greatly appreciated.


ASM

Using ASMifierClassVisitor, you can see exactly what code you need to write to generate the inner classes:

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

The rest is just determining what bits you need to parameterize in your generator code. Example output for PurchaseOrder$customer_Field which will become the file 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

(I used "inject" as the package.)

You'll also have to create synthetic accessors using ASM's visitor classes:

{
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();
}

See this project for an example of how to inject methods.


With these I totally avoid the cost of reflection.

Since this is all going to be done at runtime:

  • there is an up-front cost to this parsing and code generation
  • you'll need to discover and introspect these generated types somehow


An example using Javassist, however it does require that your properties have package level protection instead of be private

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


You could use Annotation Processors too, thus avoiding the complexity of bytecode manipulation. (see this article on javabeat)


I am surprised reflection is so much slower. If you warm up the JVM it shouldn't be more than 5x slower than direct access. BTW A micro-benchmark can give mis-leading results because a simple getter/setter can be easily optimised away to nothing if it doesn't do real work.

Another way you can avoid reflection and byte code is to use the sun.misc.Unsafe class. It has to be handled with care and its not portable to all JVMs, but it is 2-3x faster than reflection. See my essence-rmi project for examples.

Another option is to generate code and compile it on the fly. You can use the Compiler API or a library like BeanShell.

Note: if you have a private field, it cannot be accessed from another class using byte code. This is a JVM restriction. Inner and nested classes avoid this by generating accessor methods for you like access$100 in the class with the private methods (you may have seen these in your call stack) However, it means you cannot add a class to access private fields without altering the original class.


The goal is performance!

Yes, that is in very many cases the goal. But what you are doing now with the PropertyAccessor your performance goes downwards! Every time you want to get or set a property, you will have to create a new instance for customer_Field. Or you have to keep your instance. I don't see what the problem is with a simple getter or setter.

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
}

This is performance! Native code is maybe 14 times faster, but do you really need that fast? Java is great. Why? Because of it platform independence. And if you are going to make native stuff, the power of Java is gone. So and what is the difference between waiting one minute for doing everything what the programs needs to do and waiting 50 seconds. "Where is my 14 times faster?" You don't only need to get and set. You need to do something with all the data.

And I don't think it would be faster, because you are just getting and setting object instances and primitives. Native Java is created for:

  • methods which have to calculate something that would be really faster in machine code than with Java Runtime Environment (A lot of java.lang.Math methods, like sqrt(). They could program it in Java but it would be slower)
  • things Java can't do by himself, like: exit application, create sockets, write/read files , invoking other processes, etc... That isn't pure Java that is Native Machine code that that does.

So I hope I persuaded you and you will keep it by Java.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜