Động lực học lập trình Java, Phần 8: Thay thế sự phản chiếu bằng việc tạo mã potx - Pdf 20

Động lực học lập trình Java, Phần 8: Thay thế sự phản chiếu bằng việc tạo

Tạo mã thời gian chạy cung cấp một cách để thay thế sự phản chiếu bằng sự truy
cập trực tiếp nhằm đạt hiệu năng tối đa
Dennis Sosnoski, Nhà tư vấn, Sosnoski Software Solutions, Inc.
Tóm tắt: Các phần trước trong loạt bài này, bạn đã tìm hiểu hiệu năng của sự
phản chiếu chậm hơn nhiều lần so với truy cập trực tiếp như thế nào và sau đó đã
học về hoạt động lớp (classworking) với Javassist và Apache Byte Code
Engineering Library (BCEL-Thư viện kỹ thuật mã byte). Nhà tư vấn Java Dennis
Sosnoski hoàn thành loạt bài Động lực học lập trình Java của mình bằng cách giải
thích cách bạn có thể sử dụng hoạt động lớp trong thời gian chạy để thay thế mã
phản chiếu bằng mã được tạo ra để lao hết tốc độ về phía trước.
Bây giờ bạn đã thấy cách sử dụng các khung công tác Javassist và BCEL cho hoạt
động lớp (xem liệt kê các bài viết trước trong loạt bài này), tôi sẽ cho bạn thấy một
ứng dụng hoạt động lớp thực tế. Ứng dụng này đang thay thế việc sử dụng sự phản
chiếu bằng các lớp được tạo trong thời gian chạy và được nạp trực tiếp vào JVM.
Trong quá trình ráp nó lại với nhau, tôi sắp quay lại hai bài báo đầu tiên của loạt
bài này cũng như trình bày Javassist và BCEL, vì thế nó tạo ra một sự kết thúc tốt
đẹp cho những gì tạo thành một loạt các bài viết dài.
Các mã phản chiếu theo hiệu năng
Quay lại Phần 2, tôi đã cho thấy cách mã phản chiếu chậm hơn nhiều lần so với
mã trực tiếp cho cả truy cập trường và cả các cuộc gọi phương thức. Sự chậm chạp
này không phải là một vấn đề cho nhiều ứng dụng, nhưng luôn có các trường hợp
ở đó hiệu năng rất quan trọng. Trong những trường hợp này, mã phản chiếu có thể
biểu diễn một nút cổ chai thực. Mặc dù việc thay thế mã phản chiếu bằng mã được
biên dịch tĩnh có thể rất lộn xộn và trong một số trường hợp (như trong các khung
công tác ở đó các lớp hoặc các mục được mã phản chiếu truy cập được cung cấp
trong thời gian chạy, chứ không phải là một phần của cùng một quá trình xây
dựng) thậm chí không thể thực hiện được nếu không cấu trúc lại toàn bộ ứng dụng.
Hoạt động lớp cung cấp cho bạn một sự thay thế kết hợp hiệu năng của mã được
biên dịch tĩnh với tính linh hoạt của mã phản chiếu. Cách tiếp cận cơ bản ở đây là

}
}
public class ReflectAccess
{
public void run(String[] args) throws Exception {
if (args.length == 1 && args[0].length() > 0) {

// create property name
char lead = 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" + pname, 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() + ", " + bean.getValue2());

} else {
System.out.println("Usage: ReflectAccess value1|value2");

bằng mã được biên dịch tĩnh, sau đó mở rộng cho lớp cơ sở hoặc triển khai thực
hiện giao diện đó trong lớp đã tạo ra. Mã được biên dịch tĩnh sau đó có thể thực
hiện cuộc gọi trực tiếp tới các phương thức, dù các phương thức này trên thực tế
không được triển khai thực hiện trong thời gian chạy.
Trong Liệt kê 2, tôi đã định nghĩa một giao diện, IAccess, nhằm cung cấp liên kết
này cho mã đã tạo ra. Giao diện này bao gồm ba phương thức. Phương thức đầu
tiên chỉ thiết lập một đối tượng đích được truy cập. Hai phương thức khác là các
ủy quyền cho các phương thức get (nhận) và set (thiết lập) dùng để truy cập giá trị
thuộc tính int.

Liệt kê 2. Giao diện với lớp keo dán

public interface IAccess
{
public void setTarget(Object
target);
public int getValue();
public void setValue(int value);
}

Ở đây mục đích là thực hiện giao diện IAccess được tạo ra sẽ cung cấp mã để gọi
các phương thức get và set thích hợp của một lớp đích. Liệt kê 3 cho thấy một ví
dụ về giao diện này có thể được triển khai thực hiện như thế nào, giả định rằng tôi
muốn truy cập vào thuộc tính value1 của lớp HolderBean trong Liệt kê 1:

Liệt kê 3.Thực hiện ví dụ lớp keo dán

public class AccessValue1 implements IAccess
{
private HolderBean m_target;

cho thấy đoạn mã Javassist để hoàn thành các bước này, được cấu trúc như là một
cuộc gọi phương thức để lấy thông tin lớp đích và thông tin phương thức
nhận/thiết lập và trả về sự biểu diễn nhị phân của lớp được xây dựng:

Liệt kê 4. Xây dựng lớp keo dán Javassist

/** Parameter types for call with no parameters. */
private static final CtClass[] NO_ARGS = {};

/** Parameter types for call with single int value. */
private static final CtClass[] INT_ARGS = { CtClass.intType };

protected byte[] createAccess(Class tclas, Method gmeth,
Method smeth, String cname) 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);

rõ ràng mỗi lệnh bytecode cho BCEL. Như với phiên bản Javassist, tôi sẽ bỏ qua
các chi tiết thực hiện (quay lại Phần 7 để có một tổng quan về BCEL nếu có bất cứ
điều gì chưa rõ).

Liệt kê 5. Xây dựng lớp keo dán BCEL

/** Parameter types for call with single int value. */
private static final Type[] INT_ARGS = { Type.INT };

/** Utility method for adding constructed method to class. */
private 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 cname) {

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

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

try {
Object[] gargs = new Object[0];
Object[] sargs = new Object[1];
for (int i = 0; i < num; i++) {

// messy usage of Integer values required in loop
Object 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;
}


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

Sun). Ở đây tôi đã tính đến cả thời gian mã phản chiếu và thời gian mã được tạo
cho thuộc tính thứ hai trong mỗi lần chạy thử nghiệm (vì thế cặp thời gian khi sử
dụng sự tạo mã Javassist là lần đầu tiên, theo sau là cùng cặp thời gian khi sử dụng
sự tạo mã BCEL). Các thời gian thực hiện gần như nhau bất kể Javassist hay
BCEL được sử dụng để tạo các lớp keo dán, các lớp này là những gì tôi mong
muốn nhìn thấy nhưng luôn luôn thích hợp để xác nhận!

Hình 1. Tốc độ mã phản chiếu so với tốc độ mã được tạo (thời gian tính bằng
mili giây)

Như bạn thấy từ Hình 1, mã được tạo ra thực hiện nhanh hơn nhiều so với mã
phản chiếu trong mọi trường hợp. Lợi thế tốc độ với mã được tạo ra tăng lên khi
số vòng lặp lớn lên, bắt đầu ở khoảng 5:1 với các vòng lặp 2K và tăng lên đến
khoảng 24:1 với các vòng lặp 512K. Việc xây dựng và nạp lớp keo dán đầu tiên
cũng mất khoảng 320 mili giây (ms) cho Javassist và 370 ms cho BCEL, trong khi
xây dựng với lớp keo dán thứ hai chỉ mất khoảng 4 ms cho Javassist và 2 ms cho
BCEL (do phân giải đồng hồ chỉ là 1 ms, nên các thời gian này rất thô). Nếu bạn
kết hợp các thời gian này, bạn sẽ thấy rằng ngay cả đối với vòng lặp 2K việc tạo
một lớp sẽ cho tổng hiệu năng tốt hơn khi sử dụng mã phản chiếu (với tổng thời
gian thực hiện khoảng 4 đến 6 ms ms, so với khoảng 14 ms với mã phản chiếu).
Trong thực tế, tình hình nghiêng nhiều hơn về phía ủng hộ mã được tạo ra so với
chỉ thị trên biểu đồ này. Khi tôi đã cố gắng giảm nhỏ bằng 25 vòng lặp, các mã
phản chiếu vẫn còn mất đến 6 ms đến 7 ms để thực hiện, trong khi mã được tạo ra
quá nhanh để ghi nhận. Thời gian được thực hiện bởi mã phản chiếu với tổng số
đếm vòng lặp tương đối nhỏ xuất hiện để phản chiếu một số sự tối ưu hóa đang
xảy ra bên trong JVM khi đạt đến một ngưỡng; nếu tôi hạ thấp số đếm vòng lặp
dưới khoảng 20, mã phản chiếu cũng đã trở nên quá nhanh để ghi nhận. Tiến nhanh hơn trên con đường của bạn


Nhờ tải bản gốc
Music ♫

Copyright: Tài liệu đại học © DMCA.com Protection Status