5. Compile the method to further optimize the performance of the expression calculator, we have to directly compile the expression - first generate Java code according to the logic of the expression, then perform dynamically generated Java code, this method can be called compilation method . Translation of the suffix expression into a Java expression is simple, such as "$ 0 $ 1 $ 2 * " can be represented by Java Expressions "Args [0] (Args [1] * Args [2]". We want to generate The Java class selects a unique name and then writes the code into temporary files. Dynamically generated Java classes have the following form:
Public class [name of the class] IMPLEMENTS CALCULATOR {
Public int evALUATE (int [] args) {
Return Args [0] (Args [1] * Args [2]);
}
}
Here is the full code of the compiler calculator.
//Calculatorcompiler.java
Import java.util.stack;
Import java.util.StringTokenizer;
Import java.io. *;
/ / Customized class equipment
Public Class CalculatorCompiler Extends ClassLoader {
String _compiler;
String_classpath;
Public CalculatorCompiler () {
Super (ClassLoader.getsystemClassLoader ());
// Compiler type
_Compiler = System.getProperty ("Calc.compiler");
// Default compiler
IF (_compiler == null) _Compiler = "javac";
_CLASSPATH = "."
String ExtraclassPath
= System.getProperty ("Calc.classPath");
IF (EXTRACLASSPATH! = NULL) {
_CLASSPATH = _CLASSPATH
System.getProperty ("path.separator")
EXTRACLASSPATH;
}
}
Public Calculator Compile (String Expression) {
// a3
String jtext = javaexpression (expression);
String filename = "";
String classname = ""
Try {
// Create a temporary file
File javafile = file.createTempFile
"Compiled_", ".java", new file ("."));
Filename = javafile.getname ();
ClassName = filename.substring
0, filename.lastindexof ("."));
GenerateJavafile (Javafile, ClassName, Expression);
// Compiling files
InvokeCompiler (JavaFile);
// Create a Java class
BYTE [] BUF = ReadBytes (classname ".class");
Class C = DefineClass (buf, 0, buf.length);
Try {
// Create and return an instance of the class
Return (Calculator) c.newinstance ();} catch (IllegalaccessExcect e) {
Throw new runtimeException (E.GetMessage ());
} catch (instantiationException e) {
Throw new runtimeException (E.GetMessage ());
}
} catch (ioexception e) {
Throw new runtimeException (E.GetMessage ());
}
}
/ / Generate a Java file
Void GenerateJavaFile
File Javafile, String ClassName, String Expression
THROWS IOEXCEPTION {
FileOutputStream out = new fileoutputstream (javafile);
String text = "public class" ClassName
"Implements Calculator {"
"public int evALUATE (int in) {"
" JavaExpression (Expression)
"}" "}";
Out.write (Text.getbytes ());
Out.close ();
}
// Compile Java file
Void InvokeCompiler (File Javafile) throws oewception {
String [] cmd = {_compiler, "-classpath",
_CLASSPATH, JAVAFILE.GETNAME ()};
// Execute the compile command
// a1:
Process process = runtime.getRuntime (). EXEC (CMD);
Try {// Waiting for the compiler to end
Process.waitfor ();
} catch (interruptedexception e) {
}
INT VAL = process.exitvalue ();
IF (Val! = 0) {
Throw new runtimeException
"Compile error:" "Error code" VAL);
}
}
// read the file in the form of byte array
Byte [] readbytes (string filename) throws oews ooException {
// a2
File ClassFile = New File (filename);
Byte [] buf = new byte [(int) ClassFile.Length ()];
FileInputStream in = New fileinputstream (classfile);
IN.READ (BUF);
In.Close ();
Return BUF;
}
String JavaExpression (String Expression) {
Stack stack = new stack ();
StringTokenizer Toks
= New StringTokenizer (Expression);
While (toks.hasmoretoKens ()) {
String tok = toks.nextToken ();
IF (Tok.StartSwith ("$")) {
Stack.push ("ARGS [
" Integer.parseint (tok.substring (1)) "] ");} else {
INTOP = " - * /". Indexof (Tok.Charat (0));
IF (OP == -1) {
Stack.push (tok);
} else {
STRING ARG2 = (String) stack.pop ();
String arg1 = (string) stack.pop ();
Stack.push ("
" Arg1 " TOK.CHARAT (0) " Arg2 ") ")
}
}
}
Return "Return" (String) stack.pop () ";";
}
}
After a dynamically generated code, you have to compile these code. We assume that the system uses the Javac compiler, and the system's path environment variable contains the path to the Javac compiler. If Javac is not in the PAT environment variable, or you want to use other compilers, you can specify, for example, "-dcalc.compiler = jikes". If the compiler is not Javac, it is generally placed in the ClassPath of the compiler in the JAR file (RT.jar under the JRE / LIB directory). We indicate additional ClassPath members through the ClassPath property to the compiler. For example "-dcalc.classpath = c: /java/jre/lib/rt.jar". The compiler can be called by runtime.exec (String [] CMD) as an external process, and the execution result of Runtime.exec is a process object (see the code that comes "A1", which references the specific part of the code in a similar manner) . The CMD array contains the system command to be executed, where the first element must be the name of the program to be executed, and the remaining elements are all parameters passed to the executor. After launching the compilation process, we have to wait for the compiling process to run, and then get the return value of the compiler. The compilation process returns 0 means compiling success. The last issue related to the compiler is that because the compiler is run as an external process, it is best to read the output and error report of the compiler. If the compiler has encountered a lot of errors, the compiler may be in a blocking state (wait for reading). The examples of this article are just for testing performance, which is simplicity, and does not process the problem. However, in the formal Java project, this problem must be processed. After successful compilation, there will be a class file in the current directory, we have to load it with ClassLoader (comment "A2"). ClassLoader reads the BYTE array, so let's first read the contents of the class file into the BYTE array, then create a class. The type of equipment here is the simplest custom-made loader, but it is enough to complete the task of us here. After successfully load the class, create the instance of this class, then return to this instance (comment "A3").
As can be seen from the test results, the performance of the compiler calculator has improved significantly. It is also 100,000,000 calculations, now only 100-200ms, not the original 1-2 seconds. However, compilation operations also brought a lot of time overhead, calling Javac compiler to compile code for approximately 1-2 seconds, offsetting the performance of the calculator itself. However, Javac is not a high-performance compiler. If we use the high-speed compiler such as Jikes, the compile time is greatly improved, which is reduced to 100-200ms. Sixth, the most ideal plan of the generation method is of course the cost-hours performance advantage of both compiler, and avoids the cost of calling the external compiler. Below we must generate Java bytecodes directly in memory to avoid calling the external compiler, called the generated method. The format of the Java Class file is more complicated, so we have to generate files with a third-party bytecode code library. This example is used by Bcel, ie Bytecode Engineering Library. BCEL is a source code open free code base (http://sourceforge.net/projects/bcel/), can help us analyze, create, and process binary Java bytecodes. Let's first look at the calculator code list that uses BCEL directly to generate a coded code. //Calculatorgenerator.java
Import java.io. *;
Import java.util.stack;
Import java.util.StringTokenizer;
// Download BCEL Code Base from SourceForge.Net/Projects/bcel/
Import de.Fub.bytecode.classfile. *;
Import de.Fub.bytecode.Generic. *;
Import de.fub.bytecode.constants;
Public Class CalculatorGenerator
Extends ClassLoader {
Public Calculator Generate (String Expression) {
String classname =
"Calc_" System.currentTimeMillis ();
// Declaration
// b1
Classgen Classgen
= New classgen (classname, "java.lang.Object", "",
Constants.acc_public | constants.acc_super,
NEW STRING [] {"Calculator"});
// Constructor
// b2
Classgen.adDemptyconstructor (constants.acc_public);
// Add to calculate the method of expressing expression
// b3
AddevalMethod (Classgen, Expression);
BYTE [] DATA = Classgen.getjavaclass (). getBytes ();
Class C = DefineClass (Data, 0, Data.Length);
Try {
Return (Calculator) c.newinstance ();
} catch (IllegaCcessException E) {
Throw new runtimeException (E.GetMessage ());
} catch (instantiationException e) {
Throw new runtimeException (E.GetMessage ());
}
}
Private void addevalmethod
Classgen Classgen, String Expression) {
// b4
ConstantPoolgen CP = Classgen.getConstantPool (); InstructionList IL
= New instructionList ();
StringTokenizer Toks
= New StringTokenizer (Expression);
INT stacksize = 0;
INT maxStack = 0;
While (toks.hasmoretoKens ()) {
String tok = toks.nextToken ();
IF (Tok.StartSwith ("$")) {
INT varnum = integer.parseint (tok.substring (1));
// array reference
Il.Append (InstructionConstants.Aload_1);
// array serial number
Il.Append (New Push (CP, Varnum));
Il.Append (InstructionConstants.iaLoad);
} else {
INTOP = " - * /". Indexof (Tok.Charat (0));
/ / Generate an operation instruction according to the operator
Switch (OP) {
Case -1:
INT VAL = Integer.Parseint (TOK);
Il.Append (New Push (CP, VAL));
Break;
Case 0:
Il.append (InstructionConstants.iadd);
Break;
Case 1:
Il.Append (InstructionConstants.isub);
Break;
Case 2:
Il.Append (InstructionConstants.imul);
Break;
Case 3:
Il.Append (InstructionConstants.idiv);
Break;
DEFAULT:
Throw new runtimeException ("Operator Illegal");
}
}
}
Il.Append (InstructionConstants.iReturn);
// Create a method
// b5
Methodgen Method
= New methodgen (constants.acc_public, type.int,
New Type []
{
TYPE.GETTYPE ("[i")}}, new string [] {"args"},
"Evaluate", Classgen.getClassName (), IL, CP);
// b6
Method.setmaxstack ();
Method.setmaxlocals ();
// Add the method to the class
Classgen.Addmethod (Method.getMethod ());
}
}
When using BCEL, first create a ClassGen object that represents the Java class (comment "B1"). Like the previous compilation method, we have to define a unique class name. Unlike ordinary Java code, now we must clearly state the super class java.lang.object. ACC_PUBLIC declares that this class is a public type. All Java 1.0.2 or later, the Java class must declare the ACC_SUPER access tag. Finally, we specify this class to implement the Calculator interface. Second, we must ensure that the class has a default constructor (comment "B2"). For a general Java compiler, if the Java class does not define a constructor, the Java compiler will automatically insert a default constructor. Now we use BCEL directly to generate a range code, and you must explicitly declare constructor. The way to generate the default constructor with BCEL is simple, you only need to call ClassGen.AdDemptyConstructor. Finally, we have to generate an evata-express (int [] arguments) method ("B3" and "B4"). The JVM itself is based on the stack, so the process of converting the expression into a bytecode is very simple, and the stack-based calculator can be converted directly into one by section code. The instruction is collected in the order of execution to an instructionList. In addition, we have to refer to constantpoolgen to a constant pool. After INSTRUCTIONLIST is ready, we can create a Methodgen object (comment "B5"). What we want to create is a method of public type. Its return value is int, and the input parameter is an integer array (note that we use the internal representation of the integer array "[i"). In addition, we also provide the name of the parameters, but this is not required. Here, the name of the parameter is the ARGS, the name of the method is evAlate, and the last few parameters include a class name, an instructionList, and a constant pool. Defining the Java method in BCEL is strict (note "B6"). For example, a Java method must declare how much large operator stack space is a space allocated for local variables. If these values are wrong, the JVM will reject the execution method. For this example, it is not very troublesome of manual calculation, but BCEL provides several ways to analyze the bytecode, we only need to simply call the setmaxstack () and setmaxLocals () methods. So far, the entire class has been constructed. The rest of the task is to load the class into the JVM. As long as the memory has a class in the form of a BYTE array, we can call the class loader as in the compilation method. The code directly generated and the code generated by the compilation method is as fast, but the initial object creation time is greatly reduced. If the external compiler is called, it is best to 100ms more than 100ms, and only 4ms of the BCEL creation class. Seven, performance and application tables show the average object creation time of four methods, including two compilers to test. Table 2 is the expression of 5 tests, and Table 3 is to calculate the time required for these expressions 10,000,000 times. Obviously, the examples of this paper are completely for testing performance, in practical applications, it is very rare to calculate a expression of 100,000,000 expressions. However, it is necessary to parse data at runtime (XML, scripting language, query statement, etc.) is often encountered. Dynamic code generation is not necessarily applicable to each type of task, but in the following occasions should be useful: • The process is mainly determined by the runtime. • The process requires repeated execution multiple times.