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ó.
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!
- V Vì sao lập trình viên BE cần phải biết Figma?
- N Nên học Front-end hay Back-end trước?
- H Học back end cần học những gì? Lộ trình cho người mới bắt đầu
- T Tầm quan trọng của Loose Coupling trong hệ thống Backend
- L Lập trình Web nên học ngôn ngữ nào là phù hợp?
- N Nên học Front-end hay Back-end? Sự khác biệt là gì?
- P Phân biệt Front End và Back End, điểm khác nhau là gì?
- T Top 7 câu hỏi phỏng vấn Backend Developer
- L Lộ trình học MySQL từ A đến Z
- Q Quản lý realm database theo hướng micro-service trong iOS