Cách tối ưu hóa hiệu năng khi lập trình Java

Trong Java việc tối ưu hoá hiệu năng là công việc rất quan trọng, nó không chỉ giúp code 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 lập trình viên Java có kinh nghiệm luôn coi việc tối ưu hoá hiệu năng 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 lập trình viên 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 lập trình viên không để ý hoặc chưa biết cách để triển khai nó.

  Dev Java đã biết đến 20 thư viện này chưa? (P1)
  Dev Java đã biết đến 20 thư viện này chưa? (P2)

1. Tổng quát

a. Sử dụng lại đối tượng

b. Lưu ý khi viết cấu trúc điều khiển(if, else, while, …)

– Phải sắp xếp đúng thứ tự ưu tiên cho các biểu thức logic

– Xem xét vòng lặp lựa chọn có phù hợp hay không(for, while, do while)?

– Xem xét vòng lặp điều kiện có kết thúc hay không?

– Tối ưu điều kiện kiểm tra của vòng lặp for

– Xem xét việc tính toán có thể được đưa ra ngoài vòng lặp không?

– Khai báo biến tạm để tránh phải gọi hàm tính toán nhiều lần.

c. Lưu ý khi thao tác I/O với file hệ thống

– Tắt line buffering

– Sử dụng các class Reader/Writer để đọc và ghi file theo dòng

– Caching

d. Lưu ý khi thực hiện lập trình đa luồng

e. Sử dụng StringBuilder để nối xâu

f. Phải có thời gian timeout trong các giao tiếp với hệ thống ngoài

g. Tránh ghi log ra console

2. Chi tiết thực hiện

a. Sử dụng lại đối tượng

Có 2 kỹ thuật chính là: Dedicated object reuse và Object pool. Tùy theo trường hợp mà sử dụng một cách hợp lý

– Dedicated object reuse: Khi đối tượng xử lý thường xuyên một công việc lập đi lặp lại.

VD: Cần thực hiện một công việc lập đi lập lại chuyển một đối tượng kiểu Date sang kiểu String. Trong trường hợp này ta nghĩ đến việc sử dụng lại đối tượng Date Formating.

private final SimpleDateFormat dt = new SimpleDateFormat("yyyyy-mm-dd hh:mm:ss");

– Object pool:

+ Trường hợp chúng ta có một loại object(ví dụ object connection kết nối với database), và nhiều tiến trình khác nhau có thể sử dụng đoạn code mà sử dụng object này.

+ Để tránh xung đột giữa các thread trong việc dùng chung object, ta có thể sử dụng synchronized block với block object. Nhưng cách này sẽ chậm hơn việc tạo mới object trên từng thread.

+ Giải pháp trong trường hợp này là xây dụng object pool chứa tập các object có vai trò như nhau. Các thread khác nhau khi cần 1 object thì có thể lấy từ pool, sau khi dùng xong có thể trả lại object cho pool để các thread khác có thể sử dụng.

b. Lưu ý khi viết cấu trúc điều khiển(if, else, while, …)

– Phải sắp xếp đúng thứ tự ưu tiên cho các biêu thức logic

Với câu lệnh:

if(slowFunction() && (i>=0) && (j>i )){}

Phải chuyển thành

if((i>=0) && (j>i ) && slowFunction()){}

– Xem xét vòng lặp lựa chọn có phù hợp hay không(for, while, do while)?

Ví dụ:

while(!isOptimize()){
        doOptimize();
}

Nếu trạng thái đầu là chưa optimaze thì nên chuyển thành.

do{
       doOptimize();
while(!isOptimize());

– Xem xét vòng lặp điều kiện có kết thúc hay không?

public void sillyLoop(int i)
{
        while(i!=0){
               i--;
        }
}

Nếu trường hợp i<0 vòng lặp sẽ vô hạn.
Nên chuyển thành

public void sillyLoop(int i)
{
        while(i>0){
               i--;
        }
}

– Tối ưu điều kiện kiểm tra của vòng lặp for

for(int i=0; i<str.length();i++)

Nên chuyển thành

for(int i=0; len=str.length(); i<len;i++)

– Xem xét việc tính toán có thể được đưa ra ngoài vòng lặp không?

for(int i=0; i<n; i++){
double temp=Math.sin(Math.PI/5) + Math.cos(Math.PI/7);
if(check()==temp){
//
 }
}

Nên chuyển thành

double temp=Math.sin(Math.PI/5) + Math.cos(Math.PI/7);
for(int i=0; i<n; i++){
if(check()==temp){
//
 }
}

– Khai báo biến tạm để tránh phải gọi hàm tính toán nhiều lần.
Ví dụ:

if(max<calcScore()){
     max=calcScore();
}

Phải chuyển thành

temp=calcScore();
if(max<temp){
     max=temp;
}

c. Lưu ý khi thao tác I/O với file hệ thống

– Tắt line buffering: Khi sử dụng PrintStream để ghi dữ liệu ra file, thiết lập mặc định của PrintStream cho phép nó khi gặp ký tự xuống dòng(“\n”) thì sẽ thực hiện flush dữ liệu xuống file. Tuy nhiên trong trường hợp ta ghi nhiều dòng dữ liệu ngắn, việc tự động flush dữ liệu xuống file khi gặp “\n” làm giảm hiệu quả buffering.
VD: PrintStream ps=new PrintStream(bos, false);

– Sử dụng các class Reader/Writer để đọc và ghi file theo dòng: Sẽ nhanh hơn sử dụng class DataInputStream khoảng 20% và không có các vấn đề về convert byte thành character.

– Caching: Khi cần phải đọc các dòng dữ liệu trong file nhưng không theo một thức tự xác định trước, ta nên xem xét việc đọc tất cả các dòng của file trước, lưu(cache) vào vùng nhớ của chương trình trong một ArrayList hoặc Vector để tránh việc thao tác với file nhiều lần.

Kết

Như bạn đã thấy, đôi khi không cần quá nhiều công sức để cải thiện hiệu suất trong Java. Hầu hết những cách trong bài này chỉ cần thêm một sự cố gắng nhỏ là có thể áp dụng chúng vào code của bạn. Chúc anh em thành công nhé.

Đừng quên xem thêm các bài viết:

Cơ hội việc làm Java hấp dẫn tại TopDev đang chờ bạn!