Profiling: đo hiệu năng trước khi tối ưu để khỏi đoán mò

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Chương trình chạy chậm, và bạn "linh cảm" rằng hàm A là thủ phạm. Bạn mất cả buổi tối ưu hàm A rồi phát hiện tổng thời gian chạy gần như không đổi. Chuyện này xảy ra rất thường xuyên, vì trực giác của lập trình viên về hiệu năng thường sai. Giải pháp là đo trước bằng profiling.

Profiling là gì?

Profiling là quá trình đo xem chương trình dành thời gian (hoặc bộ nhớ) ở đâu: hàm nào được gọi bao nhiêu lần, mỗi lần mất bao lâu, dòng nào tốn nhiều nhất. Kết quả cho bạn bức tranh khách quan để quyết định tối ưu cái gì.

Quy tắc quen thuộc: phần lớn thời gian chạy thường dồn vào một phần nhỏ của code. Tìm ra phần nhỏ đó là chìa khóa.

Các cách đo từ đơn giản đến đầy đủ

1. Đo thời gian thủ công

Cách nhanh nhất cho thử nghiệm sơ bộ:

// JavaScriptconsole.time('xuLyDonHang');processOrders(orders);console.timeEnd('xuLyDonHang');# Pythonimport timestart = time.perf_counter()process_orders(orders)print(f"Mất {time.perf_counter() - start:.3f} giây")

Nên chạy nhiều lần và lấy trung bình hoặc trung vị, vì lần đầu thường chậm hơn do khởi tạo, bộ nhớ đệm, và máy có thể đang bận việc khác.

2. Dùng profiler tích hợp

Mỗi ngôn ngữ đều có công cụ:

Môi trườngCông cụ thường dùng
Trình duyệt / webTab Performance và Lighthouse trong DevTools
Node.jsnode --prof, --inspect với Chrome DevTools
PythoncProfile, timeit, thư viện line_profiler
PHPXdebug, Blackfire
JavaJFR (Java Flight Recorder), VisualVM
DatabaseEXPLAIN, nhật ký truy vấn chậm

Ví dụ với Python:

python -m cProfile -s cumtime my_script.py

Lệnh này sắp xếp kết quả theo tổng thời gian tích lũy, giúp bạn thấy hàm nào chiếm nhiều nhất.

Đọc kết quả profiling

  • Tổng thời gian (cumulative): thời gian cả hàm và các hàm nó gọi. Hữu ích để thấy nhánh nào nặng.
  • Thời gian riêng (self/tottime): thời gian chỉ trong thân hàm đó. Hữu ích để tìm hàm làm việc nặng thật sự.
  • Số lần gọi: hàm nhỏ nhưng gọi hàng triệu lần có thể vẫn là nút thắt.
  • Flame graph (biểu đồ ngọn lửa): trực quan hóa call stack, thanh càng rộng càng tốn thời gian. Nhiều công cụ hỗ trợ xuất biểu đồ này.

Các nút thắt phổ biến

  1. Truy vấn database: thiếu chỉ mục, truy vấn N+1 (một truy vấn lấy danh sách, rồi mỗi phần tử một truy vấn nữa).
  2. Gọi mạng và I/O: gọi API ngoài tuần tự thay vì song song, đọc ghi file lặp lại.
  3. Vòng lặp lồng nhau trên dữ liệu lớn: độ phức tạp bình phương trở lên.
  4. Làm lại việc đã làm: tính toán lặp, không cache kết quả.
  5. Cấp phát bộ nhớ quá nhiều: tạo nhiều đối tượng tạm, tải cả file lớn vào bộ nhớ.
  6. Render giao diện thừa: web hoặc ứng dụng vẽ lại không cần thiết.

Ví dụ: nút thắt N+1

# Chậm: 1 truy vấn + N truy vấnorders = db.query("SELECT * FROM orders")for order in orders:order.customer = db.query("SELECT * FROM customers WHERE id = ?", order.customer_id)# Nhanh hơn: lấy hết khách hàng cần thiết một lầnorders = db.query("SELECT * FROM orders")ids = list({o.customer_id for o in orders})customers = db.query("SELECT * FROM customers WHERE id IN (...)", ids)

Profiling sẽ cho thấy hàng trăm truy vấn nhỏ nhắc lại, dấu hiệu rất rõ của N+1. (Đoạn code trên là minh họa ý tưởng, cú pháp cụ thể tùy thư viện.)

Quy tắc đo đạc đáng tin

  • Đo trong điều kiện giống thật: dữ liệu cỡ thật, cấu hình giống production nếu có thể.
  • Chỉ thay đổi một thứ mỗi lần để biết cái gì có tác dụng.
  • Lặp lại nhiều lần và cẩn thận với nhiễu.
  • Ghi lại số liệu trước và sau để chứng minh cải thiện.
  • Đừng đo trong chế độ debug nếu thực tế chạy ở chế độ release.
  • Biết giới hạn của profiler: bản thân nó làm chương trình chậm đi và có thể bóp méo kết quả một chút.

Quy trình gợi ý

  1. Xác định vấn đề: màn hình nào, thao tác nào chậm, chậm bao nhiêu so với mục tiêu.
  2. Chạy profiler với tình huống đó.
  3. Tìm vài mục tốn nhiều thời gian nhất.
  4. Đặt giả thuyết cải thiện, thực hiện thay đổi nhỏ.
  5. Đo lại và so sánh, nếu không cải thiện thì hoàn tác.
  6. Dừng khi đạt mục tiêu.

Kết luận

Đo trước, tối ưu sau. Khi có số liệu, bạn tránh được việc tốn công vào chỗ không cần, đồng thời có bằng chứng thuyết phục khi trao đổi với đồng đội. Hãy làm quen với một công cụ profiling cho ngôn ngữ bạn dùng ngay hôm nay.

Để tìm hiểu cách xây dựng ứng dụng nhanh và ổn định, bạn có thể tham khảo các khóa học trên codebem.com.

author_photo
Nguyễn Đình Cường

0 comment