Độ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ó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 Javacủ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ẽ chobạ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.

pdf22 trang | Chuyên mục: Java | Chia sẻ: dkS00TYs | Ngày: 04/09/2014 | Lượt xem: 1042 | Lượt tải: 0download
Tóm tắt nội dung Độ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, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
nstruction 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(); 
} 
Kiểm tra hiệu năng 
Bây giờ tôi đã có mã cho cả hai phiên bản Javassist và BCEL về xây dựng phương 
thức, tôi có thể tiến hành thử nghiệm chúng để xem chúng sẽ làm việc tốt như thế 
nào. Vì lý do ban đầu của tôi về việc tạo mã trong thời gian chạy là để thay thế sự 
phản chiếu bằng một cái gì đó nhanh hơn, vì vậy sẽ thật tốt để có một sự so sánh 
hiệu năng để xem tôi đã thành công tốt như thế nào. Chỉ cần chú ý đến nó, tôi cũng 
sẽ xem xét thời gian cần thiết để xây dựng lớp keo dán với mỗi một trong các 
khung công tác. 
Liệt kê 6 thể hiện các phần chính của mã kiểm tra mà tôi sẽ sử dụng để kiểm tra 
hiệu năng. Phương thức runReflection() chạy phần phản chiếu của thử nghiệm, 
runAccess() chạy phần truy cập trực tiếp và run() kiểm soát toàn bộ quá trình (bao 
gồm cả in ra các kết quả tính thời gian). Cả hai runReflection() và runAccess() lấy 
số các vòng lặp được thực hiện làm một tham số, tham số này được chuyển lần 
lượt vào từ dòng lệnh (sử dụng mã không được hiển thị trong Liệt kê này, nhưng 
có chứa trong phần tải về). Lớp DirectLoader (ở cuối Liệt kê 6) chỉ cung cấp một 
cách dễ dàng để nạp các lớp được tạo ra. 
Liệt kê 6. Mã thử nghiệm hiệu năng 
/** 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 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; 
} 
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.class.getClassLoader()); 
 } 
 protected Class load(String name, byte[] data) { 
 return super.defineClass(name, data, 0, data.length); 
 } 
} 
Với một thử nghiệm tính thời gian đơn giản, tôi gọi phương thức run() hai lần, một 
lần cho một trong các thuộc tính trong lớp HolderBean của Liệt kê 1. Chạy qua hai 
lần thử nghiệm là quan trọng cho một thử nghiệm công bằng hợp lý -- lần vượt 
qua đầu tiên mã này sẽ nạp tất cả các lớp cần thiết, các lớp này thêm vào nhiều chi 
phí hoạt động cho cả hai quá trình tạo lớp Javassist và BCEL. Tuy vậy, chi phí 
hoạt động này không cần thiết trong lần vượt qua thứ hai, cung cấp cho bạn một sự 
đánh giá tốt hơn về việc tạo lớp sẽ yêu cầu kéo dài bao lâu khi được dùng trong 
một hệ thống thực. Đây là một ví dụ về kết quả được tạo ra khi thực hiện cuộc thử 
nghiệm này: 
[dennis]$$ java -cp .:bcel.jar BCELCalls 2000 
Generate and load time of 409 ms. 
Reflection took 61 ms. with result 2000 (2000, 0) 
Generated took 2 ms. with result 2000 (2000, 0) 
Generate and load time of 1 ms. 
Reflection took 13 ms. with result 2000 (0, 2000) 
Generated took 2 ms. with result 2000 (0, 2000) 
Hình 1 cho thấy kết quả thử nghiệm tính thời gian này khi được gọi với số đếm 
vòng lặp nằm trong phạm vi từ 2K đến 512K (các thử nghiệm chạy trên một hệ 
thống Athlon 2200 XP + đang chạy Mandrake Linux 9.1, sử dụng JVM 1.4.2 của 
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 
Bây giờ bạn đã nhìn thấy các loại hiệu năng mà hoạt động lớp trong thời gian chạy 
có thể cung cấp cho các ứng dụng của bạn. Hãy chú ý thời gian tới bạn đang phải 
đối mặt với một vấn đề tối ưu hóa hiệu năng khó điều chỉnh -- đó có thể chỉ là một 
cách giải quyết nhanh chóng để có thể tránh cho bạn khỏi việc thiết kế lại phần 
lớn. Tuy vậy, hoạt động lớp tốt cho nhiều thứ hơn chỉ là hiệu năng. Nó cũng là 
cách tiếp cận linh hoạt duy nhất để sửa đổi ứng dụng của bạn theo các yêu cầu 
trong thời gian chạy. Ngay cả khi bạn không bao giờ có lý do để sử dụng nó trong 
mã của bạn, tôi nghĩ rằng đó là một trong những tính năng của Java để duy trì 
niềm vui và sự thú vị trong việc lập trình. 
Sự mạo hiểm này trong một ứng dụng thế giới thực của hoạt động lớp kết thúc loạt 
bài về động lực học lập trình Java. Nhưng đừng tuyệt vọng -- bạn sẽ sớm có được 
cơ hội để thử một số ứng dụng hoạt động lớp tại buổi tiệc đứng của 
developerWorks khi tôi trình bày một số trong những công cụ đã được xây dựng 
xung quanh thao tác Java bytecode. Thử nghiệm đầu tiên sẽ là một bài viết về một 
cặp công cụ thử nghiệm ngoài Mother Goose. 
Mục lục 
 Các mã phản chiếu theo hiệu năng 
 Xây dựng một lớp keo dán 
 Kiểm tra hiệu năng 
 Tiến nhanh hơn trên con đường của bạn 

File đính kèm:

  • pdfĐộ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 .pdf
Tài liệu liên quan