Chuyển đổi Characters Bytes Một giải pháp phần mềm mang tính khái quát có thể ảnh hưởng đến hiệu năng của hệ thống.. Chính điều này mâu thuẫn trưc tiếp với tính tái sử dụng trong thi
Trang 1Java’s Performance
Trang 2 Hiệu năng trong các lớp Collection
Catching trong tối ưu hóa
Java I/O Stream
Trang 3chương trình
Trang 5Yêu cầu tối ưu hóa
Những yêu cầu trước khi tối ưu hóa
Code optimize được xem là một quá trình biến đổi một source code cũ sang một source code mới Việc chuyển đổi này có
những đặc tính:
• Giữ được tính chính xác
• Hiệu quả hơn cái code cũ
• Code optimize thường mất đi tính khái quái và kém khả năng sử
dụng lại Đơn giản là do nó thu hẹp phạm vi và tập trung vào vấn
đề cẩn phải giải quyết
• Code mới thường phức tạp hơn code cũ Nó có tính phức tạp, khó
hiểu, khó bảo trì,khó sửa đổi & mở rộng
Trang 6Yêu cầu tối ưu hóa
Những yêu cầu trước khi tối ưu hóa
Với những lý luận này, tối ưu hóa phần mềm là một công việc tương đối rủi ro Nó có thể tác động không tốt đến những tiêu chí khác quan trọng không kém của phần mềm vì vậy, cần phải
có sự cân nhắc khi thực hiện:
• Cần profile hóa đoạn mã cũ
• Xem xét những yếu tố trả về khi thực hiện tối ưu.
Trang 7Xử lý xâu kí tự
Xử lý xâu chuỗi là một vấn đề thường xuyên gặp khi lập trình Java, đặc
biệt là trong các ứng dụng Web
( String, StringBuffer hay StringTokenizer )
Các vấn đề tối ưu trong xâu kí tự:
1 Ghép nối xâu chuỗi
2 Quản lý đối tượng
4 Chuyển đỗi kí tự & byte
5 charAt() & startWidth()
6 StringBuffer’s Capacity
Trang 8Xử lý xâu kí tự
1. Ghép nối xâu chuỗi
ví dụ: String p = a+b; ( có bao nhiêu đối tượng được tạo ra ? )
String p = (new StringBuffer()).append(a).append(b).toString()
String s = new String();
// <+++ Start timing
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
for (int i = 0; i < 10000; i++) { s.append("a");
}
// <+++ Stop timing long stop = System.currentTimeMillis(); // Result: 8 mili s
Nhận xét : Sử dụng StringBuffer với các thao tác ghép nối, thay đổi giá trị xâu chuỗi.
Trang 10// Compare two identical strings
public class Equals_1 {
public static void main(String args[]) {
String p = "HelloWorlD";
// <+++ Start timing long start = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) {
s.equals(p);
} // <+++ Stop timing long stop = System.currentTimeMillis(); }}
Trang 11Xử lý xâu kí tự
3 So sánh chuỗi
// Compare two strings of different length
public class Equals_3 {
public static void main(String args[]) {
String s = "HelloWorld";
String p = "HelloWorld1";
// <+++ Start timing
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
Trang 12Xử lý xâu kí tự
4. Chuyển đổi Characters Bytes
public class StringGetBytes {
public static void main(String args[]) {
String s = "HelloWorld";
// <+++ Start timing
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
int i;
byte[] bytebuf = new byte[size];
for (i = 0; i < size; i++) { bytebuf = (byte) buf.charAt(i);
} return bytebuf;
} public static void main(String args[]) { String s = "HelloWorld";
long start = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) {
byte[] b = asciiGetBytes(s);
} long stop = System.currentTimeMillis(); System.out.println( " time = " + (stop - start) ); }}
// Result : 2.500 mili s
Trang 13Xử lý xâu kí tự
4. Chuyển đổi Characters Bytes
Một giải pháp phần mềm mang tính khái quát có thể ảnh hưởng đến hiệu năng của hệ thống
Một điều cơ bản của tối ưu hóa là hãy thu hẹp phạm vi vấn đề giải quyết Chính điều này mâu thuẫn trưc tiếp với tính tái sử dụng trong thiết kế phần mềm theo hướng đối tượng
Mỗi một kí tự Unicode có thể chuyển thành 2,3 byte Tuy nhiên, nếu chỉ xem xét vấn đề
giải quyết đối với những kí tự trong bảng mã ACSII - một tập hợp con nhỏ hơn của Unicode…
Trong ASCII, mỗi một kí tự được chuyển đổi thành một byte, bởi việc cắt đi một trong số
hai byte biểu diễn theo Unicode
Chính những thao tác chuyển đổi này sẽ ảnh hướng đến tính phức tạp + hiệu năng hệ
thống nếu chúng ta chỉ cần sử dụng đến những kí tự trong bảng mã ACSII
Nhận xét :
Muốn tăng hiệu năng, hãy thu hẹp phạm vi xử lý và chỉ tập trung vào vấn đề
cần phải giải quyết Hạn chế các thao tác dư thừa Ví dụ: charAt()
Trang 14Xử lý xâu kí tự
5 charAt() & startsWith()
public static byte[] asciiGetBytes(String
buf) {
int size = buf.length();
int i;
byte[] bytebuf = new byte[size];
for (i = 0; i < size; i++) {
// String begins
// It could be substring of a // larger String
}
if (s.startsWith("a")) { } if ('a' == s.charAt(0)) { }
Trang 15Xử lý xâu kí tự
6 StringBuffer’s Capacity
Để định nghĩa một đối tượng StringBuffer chúng ta có hai cách:
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer(XX);
Khởi tạo với kích thước mặc định 16
Trang 16Pure Overhead
Giới thiệu vấn đề:
Việc tối ưu hóa đỏi hỏi phải cân bằng được các yếu tố có khả năng xung đột lẫnnhau trong một phần mềm (tính mềm dẻo, khả năng bảo trì, giá cả, khả năng tái sửdụng ).Tuy nhiên vẫn có những trường hợp tối ưu mà không làm ảnh hưởng đếnnhững tiêu chí khác ( pure overhead )
Trang 17Pure Overhead
1 Những tính toán dư thừa:
public class Trace {
private static boolean loggingIsOn = false;
public static void log (String msg) {
if (loggingIsOn == true) System.out.println(msg); } public static void setLoggingIsOn(boolean newState) {
loggingIsOn = newState; } public static boolean logging() {
return loggingIsOn;
} }
Lớp Tracing gồm một biến private static : loggingIsOn giám sát trạng thái của
ứng dụng bằng cách in ra thông báo msg khi có giá trị bằng true Ví dụ Tracing mộtbiến nguyên i:
public void doIntTrace(int i) {
Trace.log("Enter doIntTrace() Input arg is " + i);
myInt = i+1;
Trace.log("Exit doIntTrace()"); }
Trang 18Pure Overhead
1 Những tính toán dư thừa
public void doInt(int i) {
myInt = i+1;
}
public void doIntTrace(int i) { Trace.log("Enter doIntTrace() Input arg is " + i);
Trang 19Pure Overhead
1 Những tính toán dư thừa
Hướng giải quyết: Kiểm tra giá trị biến trước khi thực hiện truyền tham số:
public void doIntTrace2(int i) {
if (Trace.logging()) Trace.log("Enter doIntTrace2() Input arg is " + i); myInt = i+1;
if (Trace.logging()) Trace.log("Exit doIntTrace2()");
}
Trang 20Pure Overhead
2 Tránh dư thừa trong việc tạo đối tượng
void f() { int i;
Date x = new Date();
// Date x is constructed outside
// the scope where it is used
if ( ) { // Date object x only used here }
}
void f() { int i;
if ( ) { Date x = new Date();
// Date x is constructed inside the // scope where it is used
// Date object x only used here }
}
Nếu điều kiện if là false thì việc tạo ra
biến x là dư thừa:
- Cấp phát đối tượng mới
- Thực thi khởi tạo giá trị cho đối tượng
- Thu dọn khi đối tượng không còn sử
dụng
Trang 21Pure Overhead
3 Nguyên tắc 80-20
Nguyên tắc 80-20 chỉ ra rằng : 20% thời giạn bạn code đã tạo ra những dòng lệnhmới và 80% thời gian để fix những bug trong một phần mềm, 80% thời gian thực thicủa một phần mềm bị chiếm bởi thời gian thực thi của 20% dòng lệnh nào đó Vấn
đề là làm sao để có thể tìm ra được phần 20% đó, và giải quyết vấn đề đó cho thậttốt
Ví dụ:
Một đặc tả HTTP mô tả những HTTP request có thể có mà một Web server cầnphải xử lý Các HTTP request này không phải là ngẫu nhiên , nói cách khác đa sốchúng đều có những điểm chung nhất định
Nếu như ban biết rằng, đa số các trình duyệt mà client sử dụng đều chỉ tập trungtrên môt số loại như I.E , Mozzila thì chúng ta có thể tập trung vào việc xử lý nhữngrequest đến từ các loại trình duyệt này, trước khi đưa ra những xử lý chung cho tất
cả các loại trình duyệt khác nhau
Trang 22Pure Overhead
3 Nguyên tắc 80-20
Trong HTTP request, trường HTTP Accept header xác định những định dạng nộidung mà trình duyệt có thể xử lý được Trong đặc tả HTTP, nó cho phép "Accept“header có thể sử dụng cả chữ in hoa hoặc chữ in thường, vì vậy, khi xử lý chuỗi kí
tự "Accept", chúng ta phải viết :
if (headerName.equalsIgnoreCase("Accept:")) { // This is the Accept header
}Tuy nhiên, so sánh như vậy sẽ ảnh hưởng đến hiệu năng Tại sao chúng ta khôngnghĩ đến một giải pháp tối ưu hơn : tập trung xử lý trước tiên đối với những "Accept:" của I.E, hay Mozzila, trước khi đưa ra một xử lý chung, theo đúng đặc tả
if (headerName.equals("Accept:") || headerName.equalsIgnoreCase("Accept:")) {
// This is the Accept header Do something with it
}
Trang 23Vector & Hashtable Các vấn đề:
Vector Add & Remove
Vector’s Capacity
Vector Enumeration
Xây dựng lớp Vector hiệu quả
Phương pháp sử dụng hàm API
Các tham số của Hashtable
Nâng cao tốc độ hashCode() & equals()
Trang 24Vector & Hashtable
1 Vector Add & Remove
Đối tượng Vector cung cấp cho chúng ta rất nhiều cách để bổ sung thêm một phần
Đánh giá hiệu năng giữa các phương thức
Phương thức addElement() có tốc độ thực thi nhanh hơn add(), do nó trả về
giá trị void, trong khi add() trả về một giá trị boolean
Trang 25Vector & Hashtable
2 Vector’s Capacity
Khái niệm Vector’s Capacity giống như trong StringBuffer
Quá trình mở rộng của Vector, đòi hỏi bộ nhớ phải cấp phát một mảng mới, và copy
dữ liệu từ mảng cũ sang mảng mới, xóa mảng cũ đi Chính vì thế, nó cũng sẽ tácđộng tương đối đến hiệu quả thực thi của phần mềm
Giải quyết: thay thế khởi tạo mặc định
Vector v = new Vector()
bằng Vector v = new Vector(x),với x là một giá trị ước lượng
Ngoài ra, còn một tham số nữa có thể sử dụng là mức độ gia tăng phần tử trong quátrình mở rộng, với mục đích ở đây là giảm thiểu hóa việc tác động hiệu năng khiVector bắt buộc phải mở rộng
Ví dụ: Vector v = new Vector(100,25);
Trang 26Vector & Hashtable
3 Vector Enumeration
int size = v.size();
for (int i = 0; i < size; i++) {
//result : method 1 is better than method 2
for (Enumeration enum = v.elements(); enum.hasMoreElements(); ) {
s = (String) enum.nextElement(); // Do something
}
for (Enumeration enum = v.elements();; ) {
s = (String) enum.nextElement(); // Do something
} } catch (NoSuchElementException e) {}
Trang 27Vector & Hashtable
4 Xây dựng lớp Vector hiệu quả
Xây dựng một lớp PVector hay thể cho Vector, chỉ tập trung vào tính hiệu quả của việcthực thi
public class PVector {
protected Object elementData[];
protected int elementCount;
public PVector(int initialCapacity) { elementData = new Object[initialCapacity];}
private void ensureCapacity(int minCapacity) {
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = oldCapacity * 2;
if (newCapacity < minCapacity) { newCapacity = minCapacity; }
elementData = new Object[newCapacity];
System.arraycopy(oldData, 0, elementData, 0, elementCount);
} }
Trang 28Vector & Hashtable
4 Xây dựng lớp Vector hiệu quả
public int size() { return elementCount; }
public Object elementAt(int index) {
return elementData[index];
}
public void setElementAt(Object obj, int index) { elementData[index] = obj; }
public void addElement(Object obj) {
ensureCapacity(elementCount + 1);
elementData[elementCount++] = obj;
}
public void removeAllElements() {
for (int i = 0; i < elementCount; i++)
elementData = null;
elementCount = 0;
}
Trang 29Vector & Hashtable
4 Xây dựng lớp Vector hiệu quả
public Object remove() {
Object oldValue = elementData[ elementCount];
elementData[elementCount] = null; // Let gc do its work return oldValue;
}
public String toString() {
int max = elementCount -1;
StringBuffer buf = new StringBuffer();
Trang 30Vector & Hashtable
5 Phương pháp sử dụng hàm API
{
String s1 = "HelloWorld";
Vector v = new Vector(1000);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 1000; j++) {
// Populate vector with 1,000 elements
v.addElement(s1); }
for (int size = v.size(); size > 0; ) {
String s2 = (String) v.elementAt(size-1);
v.removeElementAt( size); }
}
long stop = System.currentTimeMillis();
// <++++++++ Stop timing here
} // result : 1.600 mili s
{String s1 = "HelloWorld";
Vector v = new Vector(1000);
long start = System.currentTimeMillis(); for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 1000; j++) { // Populate vector with 1,000 elements v.addElement(s1); }
for (int size = v.size(); size > 0; ) { String s2 = (String) v.remove(size); long stop = System.currentTimeMillis(); // <++++++++ Stop timing here
} // result : 1.180 mili s
Trang 31Vector & Hashtable
5 Phương pháp sử dụng hàm API
Giả sử rằng, có một đối tượng Hashtable để lưu giữ những servlet data bởi các key
- servlet name ( một xâu kí tự ) Khi yêu cầu một serlvet data, chúng ta đưa ra servletname tương ứng, nếu serlvet data không được tìm thấy, chúng ta phải tạo một đầu vàocho nó, đưa giá trị vào bảng cho các lần truy cập về sau Công việc này được thực thiđơn giản như sau :
Trang 32Vector & Hashtable
5 Phương pháp sử dụng hàm API
Phương án giải quyết:
if ( (se = (ServletEntry) table.get(servletName)) != null) {
Trang 33Vector & Hashtable
6 Các tham số của Hashtable
Hashtable là một mảng các Bucket.Mỗi Bucket gồm có mỗi danh sách theo kiểu
dữ liệu link-list các cặp key-value
Khi một cặp key-value được chèn vào Hashtable, nó được map tới một bucket nhất
định thông qua một hàm mã hóa gọi là hashCode()
Khi lấy một giá trị tương ứng với một Key:
Giá trị Key nào được đưa tới đầu vào của hàm hashCode(), trả về index của
Bucket tương ứng
Tìm kiếm tuyến tính được thực hiện trên link-list của Bucket này để xác định vị
trị ứng với key và lấy kết quả tại đó đưa về Sự so sánh giữa các key trong link-list được thực hiện qua phương thức equals()
Các yếu tố ảnh hưởng đến tính hiệu năng :
- Tìm kiếm theo tuyến tính Chúng ta phải giảm đi chiều dài của link-list trong Bucket đến mức tối thiểu
- Hiệu năng của hàm hashCode()
- Tác động của so sánh theo phương thức equals()
Trang 34Vector & Hashtable
6 Các tham số của Hashtable
Phương án giải quyết:
a) Với hashCode()
- HashCode(), phải đảm bảo các key phải được phân bố đều qua các Bucket, tránh
trường hợp tập trung các key vào trong cùng Bucket
- Hàm hashCode() quét qua mỗi kí tự trong các đối tượng key để tạo ra giá trị index
xác định Bucket tương ứng tốc độ hashCode() tỉ lệ nghịch với chiều dài String
- Chiều dài của link-list gồm hai tham số , “capacity” và "load factor" Đối tượng
Hashtable, khởi đầu giá trị capacity là số lượng các bucket trong nó "Load factor" xác định số lượng các phần tử (số cặp key-value) mà Hashtable có thể chứa, khi vượt quá giới hạn thì số lượng các Buckets sẽ tăng lên gấp đôi - quá trình này gọi
là "rehasing"