Một số kĩ thuật tối ưu hoá mã nguồn Java - Phần 1
Trong Java việc tối ưu hoá mã nguồn Java là công việc rất quan trọng, nó không chỉ giúp mã nguồn thông thoáng hơn, giúp tiêu tốn ít tài nguyên hệ thống hơn, mà các kĩ thuật được trình bày dưới đây sẽ giúp nâng cao hiệu suất (performance) làm việc của Java khi chạy chương trình! Một LTV Java có ...
Trong Java việc tối ưu hoá mã nguồn Java là công việc rất quan trọng, nó không chỉ giúp mã nguồn thông thoáng hơn, giúp tiêu tốn ít tài nguyên hệ thống hơn, mà các kĩ thuật được trình bày dưới đây sẽ giúp nâng cao hiệu suất (performance) làm việc của Java khi chạy chương trình!
Một LTV Java có kinh nghiệm luôn coi việc tối ưu hoá mã nguồn như là 1 phần quan trọng của công việc lập trình, trang bị những kĩ thuật, thủ thuật tối ưu sẽ thể hiện một LTV có trình độ, và coi nó như 1 kĩ năng không thể thiếu khi làm việc với Java.
Các kĩ thuật dưới đây không phải là những giải thuật toán học cao siêu, cũng không phải triển khai 1 cách phức tạp, đôi khi chúng rất dễ để thực hiện nhưng rất nhiều LTV không để ý hoặc chưa biết cách để triển khai nó.
I. Các vòng lặp
1. Tránh việc gọi phương thức trong vòng lặp
- Mức độ nghiêm trọng: Cao
- Nguyên tắc: Nếu có thể hãy tránh sử dụng cuộc gọi tới các phương thức như length(), size() ... trong vòng lặp, nó sẽ giúp cải thiện hiệu suất
- Lý do: Việc sử dụng các phương thức trên trong vòng lặp sẽ gây ra rất nhiều cuộc gọi giống nhau và không cần thiết làm tăng xử lý cho chương trình
Ví dụ:
package com.rule; class Avoid_method_calls_in_loop_violation { public void method() { String str = "Hello"; for (int i = 0; i < str.length(); i++) // Vi Phạm { i++; } } }
Nên được viết thành:
package com.rule; class Avoid_method_calls_in_loop_correction { public void method() { String str = "Hello"; int len = str.length(); // Đẩy size ra bên ngoài vòng lặp for (int i = 0; i < len ; i++) { i++; } } }
2. Đưa các tính tóan bất biến ra ngoài vòng lặp
- Mức độ nghiêm trọng: Trung Bình
- Nguyên tắc: Các mã nguồn luôn cho ra một kết quả giống nhau qua từng lần lặp nên được di chuyển ra ngoài vòng lặp
- Lý do: Các tính tóan cho ra kết quả giống nhau, bất biến là không cần thiết phải gọi lại nhiều lần gây ra sự chậm trễ cho chương trình
Ví dụ 1:
package com.rule; class Loop_invariant_code_motion_violation { public void method(int x, int y, int[] z) { for(int i = 0; i < z.length; i++) { z[i] = x * Math.abs(y); // Vi phạm } } }
Nên được viết thành:
package com.rule; class Loop_invariant_code_motion_correction { public void method(int x, int y, int[] z) { int t1 = x * Math.abs(y); // Điều chỉnh for(int i = 0; i < z.length; i++) { z[i] = t1; } } }
Ví dụ 2:
public NamedElement find(NamedElement namedElement) { for (NamedElement otherNamedElement : getNamedElements()) { if (namedElement.getName().equals(otherNamedElement.getName())) { //Vi phạm return otherNamedElement; } } return null; }
Nên viết thành:
public NamedElement find(NamedElement namedElement) { String name = namedElement.getName(); // Chỉnh sửa for (NamedElement otherNamedElement : getNamedElements()) { if (name.equals(otherNamedElement.getName())) { // Chỉnh sửa return otherNamedElement; } } return null; }
Tham khảo:
- Java Performance Tunning by Jack Shirazi
- The Art of Java Performance Tuning by Ed Merks (Page 20)
3. Tránh nối chuỗi (String) trong vòng lặp
- Mức độ nghiêm trọng: Rất nghiêm trọng
- Nguyên tắc: Sử dụng StringBuffer hoặc StringBuilder thay vì sử dụng nối chuỗi String trong vòng lặp
- Lý do: String là 1 đối tượng bất biến, mỗi khi nối chuỗi gây ra tạo mới đối tượng String, số lần lặp càng lớn càng gây chậm trễ chương trình, tốn tài nguyên xử lý
Ví dụ:
package com.rule; class String_concatenation_violation { public void concatValues() { String result = ""; for (int i = 0; i < 20; i++) { result += getNextString(); // Vi phạm } } }
Nên được viết thành:
package com.rule; class String_concatenation_correction { public void concatValues(String strMainString, String strAppend1, String strAppend2) { String result = ""; StringBuilder builder = new StringBuilder(); for (int i = 0; i < 20; i++) { builder.append(getNextString()); // Sử dụng StringBuilder } result = buffer.toString(); } }
Tham khảo: http://java.sun.com/docs/books/performance/1st_edition/html/JPMutability.fm.html
4. Sử dụng biến tạm thay cho truy cập giá trị của phần tử trong mảng
- Mức độ nghiêm trọng: Cao
- Nguyên tắc: Sử dụng biến tạm để lưu trữ dữ liệu và tính toán thay vì truy cập tới giá trị của phần tử trong mảng
- Lý do: Việc truy cập tới 1 phần tử trong mảng luôn làm tốn chi phí hơn là truy cập tới 1 biến tạm bởi vì máy ảo Java (VM) phải kiểm tra các ràng buộc truy cập tới phần tử đó cũng như phải kiểm tra độ dài của mảng
Ví dụ:
public void setUpArray(int REPEAT) { for(int i = 0; i < REPEAT; i++) { countArr[0] += 10; // VI PHẠM } }
Nên viết thành:
public void setUpArray(int REPEAT) { int tempCound = coundArr[0]; // ĐẨY VÀO BIẾN TẠM for(int i = 0; i < REPEAT; i++) { tempCound += 10; // SỬA CHỮA countArr[0] = tempCount; // SỮA CHỮA } }
**Tham khảo: **
- Java Performance Tunning 2nd, by Jack Shirazi (Chapter 7 - Page: 194)
5. Sử dụng Int
- Mức độ nghiêm trọng: Trung Bình
- Nguyên tắc: Sử dụng kiểu dữ liệu int cho các chỉ số thay vì sử dụng các kiểu dữ liệu khác
- Lý do: Sử dụng kiểu dữ liệu Int cho các chỉ số là nhanh nhất so với việc sử dụng bất kì kiểu dữ liệu nào khác. VM được tối ưu để sử dụng kiểu Int.
Ví dụ:
Sử dụng int :
for(int i = 0; i < Repeat; i++)
là nhanh hơn so với sử dụng bất kì kiểu dữ liệu nào khác
for(long i = 0; i < Repeat; i++) for(double i = 0; i < Repeat; i++) for(char i = 0; i < Repeat; i++)
Tham khảo:
- Java Performance Tunning 2nd, by Jack Shirazi (Chapter 7 - 7.1.4)
6. Đặt Try Catch ra ngoài vòng lặp
- Mức độ nghiêm trọng: Trung Bình
- Nguyên tắc: Đặt các khối Try/Catch/Finally bên trong vòng lặp có thể làm chậm quá trình thực thi của chương trình
Ví dụ:
package com.rule; import java.io.InputStream; import java.io.IOException; class Place_try_catch_out_of_loop_violation { void method (InputStream is) { int ZERO = 0; int TEN = 10; int count = 0; for (int i = ZERO; i < TEN; i++) { try // VIOLATION { count += is.read(); } catch (IOException ioe) { ioe.printStackTrace(); } } } }
Nên viết thành:
package com.rule; import java.io.InputStream; import java.io.IOException; class Place_try_catch_out_of_loop_correction { void method (InputStream is) { int ZERO = 0; int TEN = 10; int count = 0; try // Bao Try Catch ra ngoài vòng lặp { for (int i = ZERO; i < TEN; i++) { count += is.read (); } } catch (IOException ioe) { ioe.printStackTrace(); } } }
Tham khảo: http://www.precisejava.com/javaperf/j2se/Loops.htm
II/ Làm việc với chuỗi
1. Sử dụng String.length() để kiểm tra chuỗi rỗng (empty)
- Mức độ nghiêm trọng: Cao
- Nguyên tắc: Sử dụng String.length() để kiểm tra chuỗi rỗng thay vì sử dụng String.equal()
- Lý do: Phương thức String.equals () là quá mức cần thiết để kiểm tra một chuỗi rỗng. Kiểm tra độ dài của chuỗi là 0 sử dụng String.length() sẽ nhanh hơn.
Ví dụ:
package com.rule; class Use_String_length_to_compare_empty_string_violation { public boolean isDocEmpty() { return doc.getContents().equals(""); // VI PHẠM } }
Nên viết thành:
package com.rule; class Use_String_length_to_compare_empty_string_correction { public boolean isDocEmpty() { return doc.getContents().length() == 0; // SỬ DỤNG LENGTH } }
Tham khảo:
- http://www.onjava.com/pub/a/onjava/2002/03/20/optimization.html?page=4
- http://www.javaperformancetuning.com/tips/rawtips.shtml
2. Sử dụng StringBuffer hoặc StringBuilder (Tham khảo 3. Tránh nối chuỗi (String) trong vòng lặp)
3. Sử dụng equalsIgnoreCase
- Mức độ nghiêm trọng: Cao
- Nguyên tắc: Sử dụng phương thức equalsIgnoreCase() để so sánh chuỗi mà không phân biệt hoa thường
Ví dụ:
public class Use_String_equalsIgnoreCase_violation { public void method() { String str = "APPPERFECT"; String str1 = "appperfect"; if(str1.toUpperCase().equals(str)) // Violation { System.out.println("Strings are equals"); } } }
Nên được viết thành:
package com.rule; public class Use_String_equalsIgnoreCase_correction { public void method() { String str = "APPPERFECT"; String str1 = "appperfect"; if(str1.equalsIgnoreCase(str)) // Correction. { System.out.println("Strings are equals"); } } }
4. Tránh sử dụng startsWith()
- Mức độ nghiêm trọng: Rất Cao
- Nguyên tắc: Tránh gọi String.startsWith () vì lý do hiệu suất
- Lý do: Trong native mã nguồn Java String.startsWith () xử lý khá nhiều thứ truớc khi cho ra kết quả, vì vậy giới hạn vùng kiểm tra bằng cách kiểm tra ký tự ở vị trí đầu tiên
Ví dụ:
package com.rule; class Avoid_startsWith_violation { public void method() { String sTemp="Data"; if (sTemp.startsWith("D")) // VIOLATION { sTemp = "data"; } } }
Nên được viết thành
package com.rule; class Avoid_startsWith_correction { public void method() { final int ZERO = 0; final char D = 'D'; String sTemp="Data"; if (sTemp.length () > ZERO && sTemp.charAt(ZERO) == D) // CORRECTION { sTemp = "data"; } } }
III/ Các trường hợp khác
1. Sử dụng System.arraycopy()
- Nguyên tắc: Sử dụng System.arraycopy() cho việc sao chép mảng
- Lý do: Sử dụng System.arraycopy() là nhanh hơn so với việc sử dụng vòng lặp để sao chép 1 mảng
Ví dụ:
package com.rule; class Use_System_arrayCopy_violation { public int[] copyArray (int[] array) { int length = array.length; int[] copy = new int [length]; for(int i=0;i<length;i++) { copy [i] = array[i]; // VIOLATION } return copy; } }
Nên viết thành:
package com.rule; class Use_System_arrayCopy_correction { public int[] copyArray (int[] array) { final int ZERO = 0; int length = array.length; int[] copy = new int [length]; System.arraycopy(array, ZERO, copy, ZERO, length); // CORRECTION return copy; } }
Tham khảo: http://www.cs.cmu.edu/~jch/java/speed.html
2. Tránh lặp lại việc Casting (ép kiểu)
- Mức độ nghiêm trọng: Cao
- Nguyên tắc: Chỉ ép kiểu 1 lần sau đó giữ tham chiếu của nó
- Lý do: Việc lặp đi lặp lại nhiều lần việc ép kiểu khiến việc xử lý chậm trễ hơn
**Ví dụ: **
package com.rule; import java.awt.Component; import java.awt.TextField; class Avoid_repeated_casting_violation { public void method(Component comp) { ((TextField) comp).setText(""); // VIOLATION ((TextField) comp).setEditable(false); // VIOLATION } }
Nên viết thành:
package com.rule; import java.awt.Component; import java.awt.TextField; class Avoid_repeated_casting_correction { public void method(Component comp) { final TextField tf = (TextField) comp; // CORRECTION tf.setText(""); tf.setEditable(false); } }
Tham khảo: Java Performance tunning by Jack Shirazi
3. Hủy các cấu trúc if else không cần thiết
- Mức độ nghiêm trọng: Trung Bình
- Nguyên tắc: Đơn giản hóa để nâng cao hiệu quả của mã và giảm kích thước của nó.
Ví dụ:
package com.rule; class Use_ternary_operator_correction { public boolean test(String value) { if(value.equals("AppPerfect")) // VIOLATION { return true; } else { return false; } } }
Nên viết thành:
package com.rule; class Use_ternary_operator_correction { public boolean test(String value) { return value.equals("AppPerfect"); // CORRECTION } }
4. Luôn sử dụng static với hằng số
- Mức độ nghiêm trọng: Bình thường
- Nguyên tắc: Nên khai báo static với các biến là hằng số
Ví dụ:
package com.rule; public class Always_declare_constant_field_static_violation { final int MAX = 1000; // VIOLATION final String NAME = "Noname"; // VIOLATION }
Nên viết thành:
package com.rule; public class Always_declare_constant_field_static_correction { static final int MAX = 1000; // CORRECTION static final String NAME = "Noname"; // VIOLATION }
5. Tránh check null truớc khi sử dụng so sánh InstanceOf
- Mức độ nghiêm trọng: Trung Bình
- Nguyên tắc: Không nên check NULL truớc khi so sánh InstanceOf
- Lý do: Không cần thiết phải check NULL trong truờwng hợp này, InstanceOf chỉ được so sánh khi đối tượng != NULL, nên việc so sánh là không cần thiết
Ví dụ:
package com.rule; public class Avoid_null_check_before_instanceof_violation { public void method(Object o) { if(o != null && o instanceof Object) // Violation. { // Do Something. } } }
Nên viết thành:
package com.rule; public class Avoid_null_check_before_instanceof_correction { public void method(Object o) { if(o instanceof Object) // Correction { // Do Something. } } }
6. Sử dụng mảng dữ liệu nguyên thủy
- Mức độ nghiêm trọng: Trung Bình
- Nguyên tắc: Sử dụng mảng dữ liệu nguyên thủy thay vì sử dụng Collection
- Lý do: Collection có thể chứa trong nó nhiều kiểu dữ liệu (kiểu object, nguyên thủy ...), vì vậy sử dụng mảng nguyên thủy giúp nâng cao hiệu suất chương trình
Ví dụ:
package com.rule; import java.util.Vector; public class Use_array_of_primitive_type_insteadof_collection_violation { public void method() { Vector vInt = new Vector(); // VIOLATION vInt.add(new Integer(1)); vInt.add(new Integer(2)); vInt.add(new Integer(3)); } }
Nên viết thành:
package com.rule; public class Use_array_of_primitive_type_insteadof_collection_correction { public void method() { int[] arrInt = new int[3]; // CORRECTION arrInt[0] = 1; arrInt[1] = 2; arrInt[2] = 3; } }
7. Tránh show Debug code
- Mức độ nghiêm trọng: Thấp
- Nguyên tắc: Nên check Debug truớc khi show Log
- Lý do: Việc Debugger vốn bản chất chỉ dành cho nhà phát triển, vì vậy hãy loại bỏ hoặc kiểm tra chúng truớc khi thực thi
Ví dụ:
package com.rule; public void Avoid_debugging_code_violation { private int count =0; public int getCount() { //... System.out.println("count ="+count); // Violation return count; } }
Nên viết thành:
package com.rule