The dynamics of Java programming, part 8th: Replacing reflection with code generation--Reprint

Source: Internet
Author: User
Tags time in milliseconds

Now that you've seen how to use the Javassist and BCEL frameworks for classworking (see a previous set of articles in this series), I'll show you an actual classworking application. This application replaces reflection with classes generated by the runtime and immediately loaded into the JVM. In the course of the comprehensive discussion, I will cite the first two articles of this series, as well as the discussions on Javassist and BCEL, so this article is a good summary of this long series of articles.

Performance of Reflection

In the 2nd part, I showed that reflection is many times slower than direct code, whether for field access or method calls. This delay is not a problem for many applications, but it is always the case that performance is critical. In this case, reflection can become a real bottleneck. However, it can be confusing to replace reflection with statically compiled code, and in some cases (such as in this framework: a class that reflects access or a project is provided at run time, rather than as part of this compilation process), it is simply not possible to replace the entire application without rebuilding it.

Classworking gives us the opportunity to combine the performance of statically compiled code with the flexibility of reflection. The basic approach here is to build a custom class at run time, in a way that can be used by generic code, which wraps the access to the target class (which was previously achieved through reflection). Once this custom class is loaded into the JVM, it is ready to run at full speed.

Setup phase

Listing 1 shows the starting point for the application. A simple bean class HolderBean and an access class are defined here ReflectAccess . The Access class has a command-line argument that must be the int name ( value1 or) of a bean class property with a value. value2 It increments the value of the specified property, and then prints out the values of the two properties before exiting.

Listing 1. Reflect a bean
public class holderbean{private int m_value1;        private int m_value2;    public int getValue1 () {return m_value1;    } public void setValue1 (int value) {m_value1 = value;    } public int getValue2 () {return m_value2;    } public void setValue2 (int value) {m_value2 = value; }}public class reflectaccess{public void Run (string[] args) throws Exception {if (args.length = = 1 &&            Args[0].length () > 0) {//Create property name char leads = Args[0].charat (0);                        String pname = Character.touppercase (lead) + args[0].substring (1); Look up the Get and set methods Method Gmeth = HolderBean.class.getDeclaredMethod ("Get" + PN            Ame, new class[0]);                        Method Smeth = HolderBean.class.getDeclaredMethod ("Set" + PName, new class[] {int.class}); Increment value using Reflection Holderbean bean = new Holderbean ();            Object start = Gmeth.invoke (bean, NULL);            int incr = ((Integer) start). Intvalue () + 1;                        Smeth.invoke (Bean, new object[] {new Integer (INCR)}); Print the Ending Values System.out.println ("Result values" + bean.getvalue1 () + "," + beans.                    GetValue2 ());        } else {System.out.println ("usage:reflectaccess value1|value2"); }    }}

Here are ReflectAccess two examples of running, to show the results:

[dennis]$ JAVA-CP. Reflectaccess value1result values 1, 0[dennis]$ JAVA-CP. Reflectaccess value2result values 0, 1
Building the Glue class

I have shown the reflected version of the code and now show how to replace the reflection with the generated class. To make this substitution work correctly involves a subtle problem that can be traced back to the discussion of class loading in part 1th of this series. The question is: I want to generate a class at run time that can be accessed from statically compiled code that accesses the class, but because the generated class does not exist for the compiler, there is no way to refer to it directly.

So how do you link statically compiled code to the generated class? The basic solution is to define a base class or interface that can be accessed with statically compiled code, and then the generated class extends the base class or implements the interface. Such statically compiled code can invoke the method directly, even if the method is only implemented at runtime.

In Listing 2, I've defined an interface to IAccess provide this link for the generated code. This interface consists of three methods. The first method simply sets the target object to be accessed. The other two methods are proxies for accessing int the Get and set methods of a property value.

Listing 2. interfaces to the Glue class
Public interface iaccess{public    void Settarget (Object target);    public int getValue ();    public void SetValue (int value);}

The intent here is to have the IAccess generation implementation of the interface provide the code that invokes the corresponding get and set methods of the target class. Listing 3 shows an example of implementing this interface, assuming I want to access the properties of the listing 1  中 HolderBean class value1 :

Listing 3. Glue Class Example implementation
public class AccessValue1 implements iaccess{    private Holderbean m_target;        public void Settarget (Object target) {        M_target = (holderbean) target;    }    public int GetValue () {        return m_target.getvalue1 ();    }    public void SetValue (int value) {        m_target.setvalue1 (value);}    }

The Listing 2 interface is designed to be used for specific properties of a particular type of object. This interface makes implementation code simple--which is always an advantage when it comes to processing bytecode--but also means that the implementation class is very specific. For each type of object and property to be accessed through this interface, a separate implementation class is required, which limits the method as a general workaround for reflection. This limitation is not an issue if you choose to use this technique only if reflection performance is really a bottleneck.

Generate with Javassist

 IAccessit is easy to generate an implementation class with Javassist for the Listing 2 interface-just create a new class that implements the interface, add a member variable for the target object reference, add an parameterless constructor, and a simple implementation method. Listing 4 shows the Javassist code that completes these steps, which constructs a method call that takes the target class and the Get/set method information as parameters and returns the binary representation of the constructed class:

Listing 4. Javassist Glue class Construction
/** Parameter types for call with no parameters. */private static final ctclass[] No_args = {};/** Parameter types for call with single int value. */private Static Final C Tclass[] Int_args = {Ctclass.inttype};p rotected byte[] createaccess (Class Tclas, Method Gmeth, Method Smeth, String C      Name) throws Exception {//Build generator for the new class String tname = Tclas.getname ();      Classpool pool = Classpool.getdefault ();      Ctclass clas = Pool.makeclass (CNAME);      Clas.addinterface (Pool.get ("IAccess"));            Ctclass target = Pool.get (tname);      Add target object field to class Ctfield field = new Ctfield (target, "M_target", clas);            Clas.addfield (field);      Add public default constructor method to class Ctconstructor cons = new Ctconstructor (No_args, clas);      Cons.setbody (";");            Clas.addconstructor (cons);       Add public settarget method Ctmethod meth = new Ctmethod (Ctclass.voidtype, "settarget",   New ctclass[] {pool.get ("Java.lang.Object")}, Clas);      Meth.setbody ("M_target = (" + tclas.getname () + ") $;");            Clas.addmethod (meth);      Add Public GetValue Method meth = new Ctmethod (Ctclass.inttype, "GetValue", No_args, Clas);      Meth.setbody ("return m_target." + gmeth.getname () + "();");            Clas.addmethod (meth);      Add Public SetValue Method meth = new Ctmethod (Ctclass.voidtype, "SetValue", Int_args, Clas);      Meth.setbody ("m_target." + smeth.getname () + "($);");            Clas.addmethod (meth); Return binary representation of completed class return Clas.tobytecode ();}

I'm not going to discuss the code in detail because if you've been following this series, most of the operations here are familiar (if you haven't read this series, read the 5th section now for an overview of using Javassist).

Generate with BCEL

Generating the implementation class for listing 2 with BCEL is  IAccess not as easy as using Javassist, but it is not very complex. Listing 5 shows the corresponding code. This code uses the same set of operations as the Javassist code in Listing 4, but takes longer to run because each bytecode directive needs to be spelled out for BCEL. As with Javassist, I'll skip the implementation details (see part 7th for an overview of BCEL if you have an unfamiliar place).

Listing 5. BCEL Glue class Construction
/** Parameter types for call with a single int value. */private static final type[] Int_args = {Type.int};/** Utility method for adding constructed method to class. */pri    vate static void Addmethod (Methodgen mgen, Classgen cgen) {mgen.setmaxstack ();    Mgen.setmaxlocals ();    Instructionlist ilist = Mgen.getinstructionlist ();    Method method = Mgen.getmethod ();    Ilist.dispose (); Cgen.addmethod (method);} Protected byte[] Createaccess (Class tclas, Java.lang.reflect.Method gmeth, Java.lang.reflect.Method Smeth, String cn    AME) {//build generators for the new class String tname = Tclas.getname (); Classgen Cgen = new Classgen (CNAME, "java.lang.Object", CNAME + ". Java", Constants.acc_public, new string[]    {"IAccess"});    Instructionfactory ifact = new Instructionfactory (Cgen);        Constantpoolgen Pgen = Cgen.getconstantpool (); //. Add target object field to class Fieldgen Fgen = new Fieldgen (constants.acc_private, New ObjectType (Tname), "M_target", Pgen);    Cgen.addfield (Fgen.getfield ());        int findex = Pgen.addfieldref (CNAME, "M_target", Utility.getsignature (Tname));    Create instruction list for default constructor instructionlist ilist = new Instructionlist ();    Ilist.append (INSTRUCTIONCONSTANTS.ALOAD_0); Ilist.append (Ifact.createinvoke ("Java.lang.Object", "<init>", Type.void, Type.no_args, Constants.invokespeci    AL));    Ilist.append (Instructionfactory.createreturn (type.void));        Add public default constructor method to class Methodgen MGen = new Methodgen (Constants.acc_public, Type.void,    Type.no_args, NULL, "<init>", CNAME, IList, Pgen);        Addmethod (MGen, Cgen);    Create instruction list for Settarget method ilist = new Instructionlist ();    Ilist.append (INSTRUCTIONCONSTANTS.ALOAD_0);    Ilist.append (instructionconstants.aload_1);    Ilist.append (New Checkcast (Pgen.addclass (Tname)));    Ilist.append (New Putfield (Findex)); Ilist.append (INstructionconstants.return);  Add Public settarget Method MGen = new Methodgen (Constants.acc_public, type.void, new type[] {type.object},    NULL, "Settarget", CNAME, IList, Pgen);        Addmethod (MGen, Cgen);    Create instruction list for GetValue method ilist = new Instructionlist ();    Ilist.append (INSTRUCTIONCONSTANTS.ALOAD_0);    Ilist.append (New GETFIELD (Findex));    Ilist.append (Ifact.createinvoke (Tname, Gmeth.getname (), Type.int, Type.no_args, constants.invokevirtual));        Ilist.append (Instructionconstants.ireturn); Add Public GetValue Method MGen = new Methodgen (Constants.acc_public, type.int, Type.no_args, NULL, "GetValue    ", CNAME, IList, Pgen);        Addmethod (MGen, Cgen);    Create instruction list for SetValue method ilist = new Instructionlist ();    Ilist.append (INSTRUCTIONCONSTANTS.ALOAD_0);    Ilist.append (New GETFIELD (Findex));    Ilist.append (instructionconstants.iload_1); Ilist.append (Ifact.createinVoke (Tname, Smeth.getname (), Type.void, Int_args, constants.invokevirtual));        Ilist.append (Instructionconstants.return); Add Public SetValue Method MGen = new Methodgen (Constants.acc_public, type.void, Int_args, NULL, "SetValue",    CNAME, IList, Pgen);        Addmethod (MGen, Cgen); Return bytecode of completed class return Cgen.getjavaclass (). GetBytes ();
Performance Check

The method constructs for the Javassist and BCEL versions have been introduced and can now be tested to see how they work. The fundamental reason for generating code at run time is to replace reflection with something faster, so it's best to add performance comparisons to understand improvements in this area. To make it even more interesting, I'll compare the time it takes to construct the glue class in two frameworks.

Listing 6 shows the main parts of the test code used to check performance. runReflection()The method runs the reflection portion of the test, runAccess() runs the direct Access section, and run() controls the entire process (including the print time result). runReflection()and the runAccess() number of times to execute as arguments, this parameter is passed as a command line (the code used is not shown in the manifest, but is included in the download). The DirectLoader class (at the end of listing 6) provides an easy way to load the generated classes.

Listing 6. Performance Test Code
/** Run timed loop using reflection for access to value.    */private int runreflection (int num, method Gmeth, Method Smeth, Object obj) {int value = 0;        try {object[] Gargs = new Object[0];        object[] Sargs = new Object[1]; for (int i = 0; i < num; i++) {//Messy usage of the Integer values required in loop OBJ            ECT result = Gmeth.invoke (obj, Gargs);            Value = ((Integer) result). Intvalue () + 1;            Sargs[0] = new Integer (value);                    Smeth.invoke (obj, Sargs);        }} catch (Exception ex) {ex.printstacktrace (system.err);    System.exit (1); } return value; /** Run timed loop using generated class for access to value.    */private int runaccess (int num, IAccess access, Object obj) {access.settarget (obj);    int value = 0;        for (int i = 0; i < num; i++) {value = Access.getvalue () + 1;    Access.setvalue (value); } return value; public void run (String name, int count) throws Exception {//Get instance and Access methods Holderbean bean = new Holderbean ();    String pname = name;    Char lead = Pname.charat (0);    PName = Character.touppercase (lead) + pname.substring (1);    Method gmeth = null;    Method smeth = null;        try {gmeth = HolderBean.class.getDeclaredMethod ("Get" + pname, new class[0]);    Smeth = HolderBean.class.getDeclaredMethod ("Set" + PName, new class[] {int.class});        } catch (Exception ex) {System.err.println ("No Methods found for property" + pname);        Ex.printstacktrace (System.err);    Return    }//Create the Access class as a byte array long base = System.currenttimemillis ();    String cname = "Iaccess$impl_holderbean_" + gmeth.getname () + "_" + smeth.getname ();        byte[] bytes = createaccess (Holderbean.class, Gmeth, Smeth, CNAME); Load and construct an instance of the class class Clas = S_classloader.load (CNAME, bytes);    IAccess access = null;    try {access = (IAccess) clas.newinstance ();        } catch (Illegalaccessexception ex) {ex.printstacktrace (system.err);    System.exit (1);        } catch (Instantiationexception ex) {ex.printstacktrace (system.err);    System.exit (1);        } System.out.println ("Generate and load time of" + (System.currenttimemillis ()-base) + "Ms.");    Run the timing comparison long start = System.currenttimemillis ();    int result = Runreflection (count, Gmeth, Smeth, Bean);    Long time = System.currenttimemillis ()-Start;  System.out.println ("Reflection took" + Time + "Ms. With result" + result + "(" + bean.getvalue1 () + ","    + bean.getvalue2 () + ")");    Bean.setvalue1 (0);    Bean.setvalue2 (0);    Start = System.currenttimemillis ();    result = Runaccess (count, access, bean);    Time = System.currenttimemillis ()-Start; System.out.println ("Generated took" + Time + "Ms. With result" + result + "(" + bean.getvalue1 () + "," + bean.getvalue2 () + ")"); /** simple-minded loader for constructed classes. */protected Static class Directloader extends secureclassloader{protected directloader () {Super (Timecalls.clas    S.getclassloader ());    } protected Class Load (String name, byte[] data) {return Super.defineclass (name, data, 0, data.length); }}

For a simple timing test, I called the run() method two times and  HolderBean called once for each property in the listing 1 class. Running two Tests is important for the fairness of the test-the first run of the code loads all the necessary classes, which adds a lot of overhead to the Javassist and BCEL class generation processes. However, this overhead is not required for the second run, so that it is better to estimate how long it will take to generate the class when used in the actual system. The following is a sample output that is generated when a test is executed:

[dennis]$$ java-cp.: Bcel.jar bcelcalls 2000Generate and load time of 409 Ms. Reflection took Ms. With result (0) Generated took 2 Ms. With result (0) Generate and load time of 1 Ms. Reflection took. With result (0, a) Generated took 2 Ms. with result 2000 (0, 2000)

Figure 1 shows the results of the timing test with calls from 2k to 512k cycles (Run tests on Athlon 2200+ XP systems running Mandrake Linux 9.1, using the Sun 1.4.2 JVM). Here, I add the reflection time of the second property to the time of the generated code for each test run (so that the first two times are generated using the Javassist code, and then the same two times when the BCEL code is generated). Whether it's using Javassist or BCEL to generate the glue class, the execution time is roughly the same, and that's what I expected-but it's always good to be sure!

Figure 1. Speed of reflection and generated code (time in milliseconds)

As you can see from Figure 1, the generated code executes much faster than reflection, regardless of the circumstances. The speed advantage of the generated code increases with the number of cycles, approximately 5:1 at 2k Cycles, and increases to about 24:1 in 512K cycles. For Javassist, it takes about 320 milliseconds (ms) to construct and load the first glue class, whereas for BCEL it is 370 ms, whereas the second glue class is only 4 ms for Javassist and 2 ms for BCEL (because the clock resolution is only 1ms, so these times are very sketchy). If you combine these times together, you will see that even for 2k cycles, generating a class is better than using reflection for overall performance (the total execution time is about 4 MS to 6 MS, while reflection is approximately ms).

In addition, the actual situation is more beneficial to the generated code than is shown in this figure. When the loop is reduced to 25 cycles, the reflection code still executes 6 MS to 7 MS, and the generated code runs too fast to record. For a relatively small number of cycles, the reflection time reflects a certain optimization in the JVM when a threshold is reached, and if I reduce the number of loops to less than 20, the reflection code will be too fast to record.

Speeding up the road

You have now seen what the runtime classworking can do for your application. Remember it the next time you face a difficult performance optimization problem-it's probably the key to avoiding big redesign. However, classworking not only has performance benefits, it is also a flexible way to make your application fit for runtime requirements. Even if there's no reason to use it in your code, I think it's a Java feature that makes programming interesting.

The discussion of a classworking real-world application ended the "Dynamic nature of Java programming" series. But don't be disappointed-when I show some of the tools built to manipulate Java bytecode, you'll soon have a chance to learn about some of the other classworking applications in developerWorks . The first is an article on the two test tools that mother Goose directly launches.

Original: http://www.ibm.com/developerworks/cn/java/j-dyn0610/index.html

The dynamics of Java programming, part 8th: Replacing reflection with code generation--Reprint

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.