Quy tắc số một của tối ưu: đừng đoán. Lập trình viên gần như luôn đoán sai chỗ nào chậm. Hãy đo, rồi mới sửa.
Đo một đoạn nhỏ với timeit
import timeit
timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
timeit.timeit('"-".join(map(str, range(100)))', number=10000) # thường nhanh hơn
Tìm điểm nghẽn với cProfile
python -m cProfile -s cumtime chuong_trinh.py | head -20
Cột cumtime cho biết mỗi hàm ngốn bao nhiêu giây tính cả các hàm con. Hãy tập trung vào 2-3 dòng đầu, bỏ qua phần còn lại.
Chọn đúng cấu trúc dữ liệu
ds = list(range(1_000_000))
tap = set(ds)
999_999 in ds # duyệt tuần tự — chậm, O(n)
999_999 in tap # tra bảng băm — gần như tức thì, O(1)
Đổi list sang set khi việc chính là kiểm tra “có tồn tại không” thường mang lại cải thiện lớn hơn mọi thủ thuật vi mô.
Đừng nối chuỗi trong vòng lặp
# Chậm: mỗi lần cộng tạo một chuỗi mới
kq = ""
for tu in tu_vung:
kq += tu
# Nhanh
kq = "".join(tu_vung)
Đưa việc ra khỏi vòng lặp
# Chậm
for dong in du_lieu:
mau = re.compile(r"\d+") # biên dịch lại mỗi vòng
mau.findall(dong)
# Nhanh
mau = re.compile(r"\d+")
for dong in du_lieu:
mau.findall(dong)
Nhớ kết quả với lru_cache
from functools import lru_cache
@lru_cache(maxsize=None)
def tinh_nang(n):
... # hàm thuần, cùng đầu vào luôn cho cùng đầu ra
Khi Python thuần không đủ nhanh
- Tính toán trên mảng số lớn — chuyển sang NumPy, thường nhanh hơn 10-100 lần.
- Bảng dữ liệu rất lớn — thử Polars thay cho Pandas.
- Chờ mạng nhiều — dùng asyncio hoặc threading.
- Nghẽn CPU nhiều nhân — dùng multiprocessing.
Và luôn nhớ: code chạy nhanh nhưng không ai đọc nổi là một khoản nợ. Chỉ tối ưu phần đã đo được là nghẽn thật, phần còn lại hãy ưu tiên sự rõ ràng.
