Module timeit: Đo Tốc Độ Thực Thi Code Python Chuẩn Xác & Tối Ưu Hiệu Năng
- Home
- Module timeit: Đo Tốc Độ Thực Thi Code Python Chuẩn Xác & Tối Ưu Hiệu Năng
Trong quá trình phát triển ứng dụng Python, việc tối ưu hóa hiệu năng (performance tuning) là một bước không thể thiếu. Tuy nhiên, nếu đo thời gian chạy của code bằng các phương pháp thủ công như time.time() hay time.perf_counter(), kết quả thu được thường bị nhiễu do ảnh hưởng bởi các tác vụ nền của hệ thống, cơ chế Garbage Collection (GC) hoặc độ phân giải của đồng hồ hệ thống.
Để giải quyết triệt để vấn đề này, Python cung cấp sẵn thư viện chuẩn timeit. Đây là công cụ đo hiệu năng chính xác, tự động cô lập môi trường và chạy thử nghiệm nhiều lần để đưa ra con số thực tế nhất.
Khi đo thời gian chạy của một đoạn mã, nhiều lập trình viên có thói quen viết:
import time
start = time.time()
# Do something
end = time.time()
print(end - start)
Hạn chế của cách làm trên:
Nhiễu từ hệ thống: OS có thể thực hiện chuyển ngữ cảnh (context switch), cập nhật hệ thống hoặc đọc/ghi đĩa đúng lúc đoạn code đang chạy, khiến thời gian đo bị chênh lệch lớn giữa các lần thử.
Tác động của Garbage Collector: Trình thu gom rác của Python có thể kích hoạt ngẫu nhiên trong lúc đo, làm giảm tốc độ xử lý.
Độ phân giải đồng hồ: time.time() phụ thuộc vào đồng hồ hệ thống và có thể bị điều chỉnh bởi NTP (Network Time Protocol).
Ưu điểm của timeit:
Chạy lặp lại nhiều lần: Tự động thực hiện mã hàng nghìn hoặc hàng triệu lần để lấy trung bình hoặc giá trị nhỏ nhất (best case).
Tắt Garbage Collection theo mặc định: Giúp loại bỏ nhiễu do GC gây ra trong quá trình benchmark.
Sử dụng bộ đếm thời gian tốt nhất: Tự động chọn time.perf_counter() hoặc time.process_time() tùy thuộc vào HĐH để đảm bảo độ chính xác cao nhất.
timeit.timeit()Hàm timeit.timeit(stmt, setup, number, globals) nhận vào các tham số chính:
stmt (statement): Đoạn mã Python cần đo (dạng chuỗi hoặc callable).
setup: Đoạn mã chuẩn bị (khai báo biến, import thư viện) chạy 1 lần duy nhất trước khi đo.
number: Số lần lặp lại đoạn mã stmt (mặc định là 1,000,000 lần).
globals: Truyền biến cục bộ/toàn cục vào môi trường đo.
map()import timeit
# So sánh thời gian tạo danh sách số chính phương
stmt_comprehension = "[x**2 for x in range(1000)]"
stmt_map = "list(map(lambda x: x**2, range(1000)))"
time_comp = timeit.timeit(stmt=stmt_comprehension, number=10000)
time_map = timeit.timeit(stmt=stmt_map, number=10000)
print(f"List Comprehension: {time_comp:.5f} giây")
print(f"Map Function: {time_map:.5f} giây")
Nếu đã định nghĩa sẵn một hàm trong script, bạn có thể truyền globals=globals() để timeit nhận diện được hàm đó mà không cần viết dưới dạng chuỗi:
import timeit
def process_data():
return sum(i * i for i in range(10000))
# Đo hàm process_data chạy 1,000 lần
execution_time = timeit.timeit(process_data, number=1000)
print(f"Thời gian thực thi: {execution_time:.6f} giây")
Thay vì chỉ đo một chu kỳ (looping number lần), hàm timeit.repeat() chạy thử nghiệm qua nhiều chu kỳ (repeat) và trả về một danh sách kết quả. Điều này giúp bạn tìm ra thời gian thực thi nhanh nhất (best-case execution time), loại bỏ các khoảng thời gian bị chậm do nghẽn CPU tạm thời.
import timeit
setup_code = "data = list(range(10000))"
stmt_code = "5000 in data"
# Chạy 5 chu kỳ, mỗi chu kỳ lặp 1000 lần
results = timeit.repeat(stmt=stmt_code, setup=setup_code, repeat=5, number=1000)
print("Kết quả các chu kỳ:", results)
print(f"Thời gian tối ưu nhất: {min(results):.6f} giây")
Mẹo: Trong đo kiểm hiệu năng, giá trị nhỏ nhất (
min(results)) thường phản ánh đúng nhất hiệu năng thực tế của mã nguồn vì nó ít chịu ảnh hưởng nhất bởi các tác vụ bên ngoài.
Bạn có thể sử dụng timeit trực tiếp từ giao diện dòng lệnh mà không cần sửa file .py:
# Đo thời gian nối chuỗi bằng join() vs cộng chuỗi (+)
python -m timeit "'-'.join(str(n) for n in range(100))"
python -m timeit "s = ''; for n in range(100): s += str(n)"
Kết quả trả về từ CLI sẽ tự động tính toán số lần lặp tối ưu và đưa ra thời gian trung bình kèm độ lệch chuẩn:
20000 loops, best of 5: 12.4 usec per loop
Tránh biến toàn cục không cần thiết: Truy cập biến toàn cục (global variables) trong Python chậm hơn biến cục bộ (local variables). Hãy đảm bảo việc đo kiểm phản ánh đúng phạm vi biến thực tế.
Không đo I/O operations: timeit thiết kế cho việc đo hiệu năng CPU/RAM. Tránh dùng timeit để đo truy vấn Database, gọi API, hoặc đọc/ghi file vì độ trễ mạng/đĩa sẽ làm sai lệch hoàn toàn kết quả.
Cân nhắc dung lượng dữ liệu: Với các thuật toán có độ phức tạp lớn $O(n^2)$, $O(2^n)$, cần hạ tham số number xuống phù hợp (ví dụ number=100 hoặc number=10) để tránh treo chương trình.
Thư viện timeit là công cụ đơn giản nhưng cực kỳ mạnh mẽ giúp lập trình viên Python đánh giá và tối ưu hóa đoạn mã của mình một cách khoa học. Việc lựa chọn cấu trúc dữ liệu và giải thuật tối ưu dựa trên số liệu đo đạc thực tế từ timeit sẽ giúp ứng dụng của bạn vận hành nhanh và tiết kiệm tài nguyên hơn đáng kể.