30/09/2018, 18:28

Exception Handling in Java – The Bad Parts

Trong bài lần trước, Exception Handling in Java – The Good Parts, tôi đã nêu ra các lợi ích của việc sử dụng Exception Handling trong Java. Tuy vậy, nếu sử dụng sai chỗ, các Exception có thể khiến performance của chương trình bị ảnh hưởng ít nhiều.

Chắc hẳn trong chúng ta ai cũng có lần phải check xem chuỗi (String) nhập vào có phải số nguyên hay không, dù là làm bài tập, hay làm project. Tôi đã từng rất vui khi tìm ra một mẹo có thể xử lý vấn đề này một cách nhanh gọn như sau:

public boolean checkInt(String input) {
    try {
        Integer.parseInt(input);
        return true;
    } catch (NumberFormatException e) {
        return false;
    }
}

Cho đến khi tôi thấy method này:

public boolean isInteger(String str) {
    if (str == null) {
        return false;
    }
    int length = str.length();
    if (length == 0) {
        return false;
    }
    for (int i = 0; i < length; i++) {
        char c = str.charAt(i);
        if (c <= '/' || c >= ':') {
            return false;
        }
    }
    return true;
}

Vâng, tôi đã thử check xem cái method dài dài kia thì có gì hay hơn cái mẹo mà tôi vẫn hay dùng dù cùng công dụng là check xem có phải số nguyên hay không. Tôi tạo một vòng for và check 10 triệu lần:

Trường hợp chuỗi nhập vào là số nguyên:

public static void main(String[] args) {
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 10_000_000; i++) {
        checkInt("50");
    }
    long endTime = System.currentTimeMillis();
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for (int i = 0; i < 10_000_000; i++) {
        isInteger("50");
    }
    endTime = System.currentTimeMillis();
    System.out.println(endTime - startTime);
}

Kết quả là 16 và 4. Cái method dài kia nhanh hơn method của tôi chẳng đáng là bao, mà code thì lằng nhằng. Hihi.

Trường hợp chuỗi nhập vào không phải số nguyên:

public static void main(String[] args) {
    //Generate startTime
    long startTime = System.currentTimeMillis();
    //Loop 10_000_000 times to check Integer.parseInt()
    for (int i = 0; i < 10_000_000; i++) {
        checkInt("a");
    }
    long endTime = System.currentTimeMillis();
    //Print out result
    System.out.println(endTime - startTime);

    //Generate startTime
    startTime = System.currentTimeMillis();
    //Loop 10_000_000 times to check isInteger()
    for (int i = 0; i < 10_000_000; i++) {
        isInteger("a");
    }
    endTime = System.currentTimeMillis();

    //Print out result
    System.out.println(endTime - startTime);
}

Tôi giật mình vì kết quả là 7166 và 3. Khoảng cách quá xa và chương trình chạy khá lâu. CPU i5 của tôi cũng sử dụng khoảng 40% trong quá trình chương trình chạy. Tại sao lại như vậy nhỉ?

1. Khái niệm Context-Switch

Khái niệm context-switch có thể rất lạ với một sinh viên chỉ học lập trình phần mềm mà không học gì về khoa học máy tính (như tôi). Bạn có thể hiểu như sau: Context-Switch là quá trình lưu và khôi phục luồng xử lý hiện tại của một process hoặc một thread. Như vậy luồng xử lý đó có thể chạy tiếp vào một thời điểm nào đó sau khi được lưu lại.

Context-switch có thể được sử dụng ở cả phần mềm và phần cứng. Nó là khái niệm cốt lõi của một hệ điều hành đa nhiệm. Trong các chương trình phần mềm, quá trình này sẽ xảy ra gián tiếp khi có bất kì process nào làm gián đoạn luồng xử lý của chương trình và chuyển qua một luồng xử lý khác.

2. Context-Switch trong Java và cái giả phải trả cho Exception-Handling

Dễ thấy, quá trình xử lý Exception của Java sẽ làm xảy ra gián tiếp quá trình Context-Switch, do flow của chương trình bị gián đoạn khi Exception được ném ra. Trong các chương trình Java, quá trình context-switch luôn luôn gây ra sự tốn kém: Non-Reentran Data, Heap + Stack paged out và được lưu lại để phục vụ cho việc quay trở lại ngẫu nhiên, và một môi trường mới sẽ được paged-in. (Bạn đọc hãy vào link wikipedia đọc để hiểu rõ nhất các khái niệm tôi đưa ra, vì khả năng dịch tiếng anh chuyên ngành của tôi có hạn. Rất xin lỗi các bạn).

Chốt lại: Rất tốn tài nguyên –> performance của chương trình bị ảnh hưởng.

3. Khi nào dùng Exception-Handling?

Đây là một câu hỏi không hề có câu trả lời cụ thể. Càng vào code nhiều, bạn sẽ càng hiểu được những trường hợp nào nên dùng, những trường hợp nào thì không. Tôi sẽ nêu ra một vài trường hợp bạn có thể nhận biết được sự nguy hiểm như sau:

  • Đặt try catch trong vòng lặp for – Rất nguy hiểm, cần cân nhắc kĩ. Bạn hoàn toàn có thể đặt for trong try/catch block, nhưng điều ngược lại thì cần phải xem có thực sự cần thiết không.
  • Nếu bạn buộc phải catch một exception và không làm gì với nó cả thì hãy dùng throws.

Quan điểm của mình:

  • Nếu lỗi là một lỗi phổ biến (check số nguyên, chia cho 0, người dùng nhập sai pass…blah…blah…) thì không catch exception
  • Nếu không thể làm gì để xử lý exception thì không catch nó.
Bài liên quan
0