Python chạy chậm — đây là sự thật mà hầu hết developer đều biết nhưng hay bỏ qua, cho đến khi đụng phải bài toán tính toán nặng thật sự.
Mình từng viết một script xử lý 100K records để tính similarity score giữa các chuỗi văn bản. Chạy xong mất 8 phút. Khách hàng cần kết quả trong 30 giây. Sau khi port phần tính toán sang Cython, cùng script đó chạy xong trong 19 giây. Cùng logic, cùng kết quả — chỉ là compiled sang C.
So sánh các cách tăng tốc Python phổ biến
Python có vài hướng tăng tốc khác nhau. Biết cái nào phù hợp với loại bài toán nào sẽ giúp bạn tránh mất công optimize sai chỗ.
1. Thuật toán tốt hơn + Python built-in
Đây là bước đầu tiên nên làm trước khi nghĩ đến bất cứ thứ gì khác. Built-in như map(), filter(), list comprehension thường nhanh hơn vòng lặp for thông thường vì chúng được implement bằng C bên dưới. Tuy nhiên bạn vẫn đang chạy Python interpreter — overhead vẫn còn đó.
2. NumPy / Pandas vectorization
Thay vì loop qua từng phần tử, bạn thực hiện phép toán trên toàn bộ array cùng lúc. NumPy operations chạy bằng C bên dưới, nên nhanh hơn pure Python rất nhiều. Hướng này phù hợp khi bài toán biểu diễn được dưới dạng matrix/array operations.
3. Cython — biên dịch Python sang C
Cython là một superset của Python: bạn viết code gần như Python bình thường, thêm vào một số type annotations, rồi Cython biên dịch thành C extension. Kết quả là module chạy tốc độ C nhưng vẫn import từ Python như bình thường.
4. ctypes / C extension thủ công
Dùng khi bạn đã có thư viện C sẵn hoặc muốn tự viết C rồi gọi từ Python. Cách này đòi hỏi biết C và quản lý memory thủ công — ngưỡng tiếp cận cao hơn nhiều so với Cython.
Phân tích ưu và nhược điểm của từng approach
Pure Python tối ưu
- Ưu: Không cần tool nào thêm, dễ debug, code đơn giản
- Nhược: Vẫn bị bottleneck bởi Python interpreter và GIL
- Mức tăng tốc thực tế: 2–5x so với naive implementation
NumPy vectorization
- Ưu: Dễ dùng nếu bài toán phù hợp, ecosystem phong phú
- Nhược: Logic phức tạp với nhiều điều kiện rẽ nhánh rất khó biểu diễn dạng array operation
- Mức tăng tốc thực tế: 10–100x cho numeric operations
Cython
- Ưu: Giữ nguyên Python syntax quen thuộc, tăng tốc ấn tượng cho bất kỳ loại logic nào, có thể gọi trực tiếp C library
- Nhược: Cần bước compile thêm, setup build environment, debug khó hơn pure Python một chút
- Mức tăng tốc thực tế: 10–150x tùy mức độ type annotation
ctypes / C extension thủ công
- Ưu: Kiểm soát tuyệt đối, hiệu suất tối đa
- Nhược: Cần biết C, dễ memory leak, tốn nhiều thời gian viết
- Mức tăng tốc thực tế: Tương đương Cython trong hầu hết trường hợp thực tế
Khi nào nên chọn Cython?
Chọn Cython khi bạn đang gặp đủ các điều kiện sau:
- Đã profile và xác định được bottleneck là một hàm CPU-bound cụ thể
- Logic của hàm đó phức tạp — nhiều loop lồng nhau, điều kiện rẽ nhánh — khó vectorize với NumPy
- Muốn tăng tốc mà không cần học C từ đầu
- Cần tích hợp với C/C++ library hiện có
Bài toán thuần numeric như matrix multiplication hay convolution? NumPy hoặc PyTorch/JAX là lựa chọn tốt hơn — các thư viện này được tối ưu sâu ở cấp hardware, Cython không cạnh tranh được ở đây. Cython mạnh nhất khi logic xử lý phức tạp, nhiều điều kiện rẽ nhánh mà NumPy không diễn đạt được gọn.
Hướng dẫn dùng Cython từng bước
Bước 1: Cài đặt
pip install cython numpy
# Cần có C compiler:
# Ubuntu/Debian:
sudo apt install gcc python3-dev
# macOS:
xcode-select --install
# Windows: cài Visual Studio Build Tools (chọn "Desktop development with C++")
Bước 2: Viết module Cython (.pyx)
Ví dụ cụ thể: hàm tính tổng bình phương của một dãy số — loại tính toán mà Python thuần rất chậm với dữ liệu lớn. Tạo file fast_math.pyx:
# fast_math.pyx
def sum_squares_python(numbers):
"""Phiên bản Python thuần — để so sánh baseline"""
total = 0
for x in numbers:
total += x * x
return total
def sum_squares_cython(list numbers):
"""Cython với type annotations — loại bỏ dynamic typing overhead"""
cdef double total = 0.0
cdef double x
cdef int i
cdef int n = len(numbers)
for i in range(n):
x = numbers[i]
total += x * x
return total
def sum_squares_array(double[:] arr):
"""Typed memoryview — truy cập trực tiếp vào memory của numpy array"""
cdef double total = 0.0
cdef int i
cdef int n = arr.shape[0]
for i in range(n):
total += arr[i] * arr[i]
return total
Hai từ khóa quan trọng nhất cần hiểu:
cdef double x— khai báo biến kiểu C, loại bỏ hoàn toàn overhead dynamic typing của Pythondouble[:] arr— typed memoryview, cho phép truy cập trực tiếp vào bộ nhớ của numpy array mà không qua Python object layer
Bước 3: Tạo setup.py để compile
# setup.py
from setuptools import setup
from Cython.Build import cythonize
import numpy as np
setup(
name="fast_math",
ext_modules=cythonize(
"fast_math.pyx",
compiler_directives={
"language_level": "3",
"boundscheck": False, # Tắt kiểm tra bounds — nhanh hơn nhưng cần đảm bảo index hợp lệ
"wraparound": False, # Tắt negative indexing (-1, -2...)
}
),
include_dirs=[np.get_include()],
)
Bước 4: Compile
python setup.py build_ext --inplace
Compile xong, thư mục của bạn sẽ có thêm file fast_math.cpython-3XX-linux-gnu.so (Linux) hoặc .pyd (Windows). C extension đã sẵn sàng — import bình thường như bất kỳ module Python nào.
Bước 5: Benchmark để thấy sự khác biệt
# benchmark.py
import time
import numpy as np
import fast_math
data = list(range(1_000_000))
arr = np.array(data, dtype=np.float64)
def measure(label, fn, *args):
start = time.perf_counter()
result = fn(*args)
elapsed = time.perf_counter() - start
return elapsed, result
t_py, r1 = measure("Python", fast_math.sum_squares_python, data)
t_cy, r2 = measure("Cython list", fast_math.sum_squares_cython, data)
t_arr, r3 = measure("Cython array", fast_math.sum_squares_array, arr)
print(f"Python thuần: {t_py:.4f}s")
print(f"Cython (list): {t_cy:.4f}s → {t_py/t_cy:.1f}x nhanh hơn")
print(f"Cython (ndarray): {t_arr:.4f}s → {t_py/t_arr:.1f}x nhanh hơn")
Kết quả thực tế (Python 3.11, Intel i7):
Python thuần: 0.2847s
Cython (list): 0.0312s → 9.1x nhanh hơn
Cython (ndarray): 0.0018s → 158.2x nhanh hơn
Cùng logic, cùng kết quả, nhưng nhanh hơn 158 lần khi dùng typed memoryview. Đây chính xác là mức cải thiện mình đạt được với script xử lý 100K records — từ 8 phút xuống còn dưới 20 giây.
Bonus: Xem annotation để biết chỗ nào cần tối ưu thêm
Cython có công cụ rất hữu ích: generate HTML report hiển thị chỗ nào trong code vẫn còn Python overhead (màu vàng càng đậm = càng chậm = cần thêm type annotation):
cython -a fast_math.pyx
# Mở file fast_math.html trong browser để xem
Những điều cần lưu ý trước khi dùng Cython
- Profile trước, optimize sau: Dùng
cProfilehoặcline_profilerđể xác định đúng bottleneck. Tối ưu sai chỗ thì mất công mà không ra gì. - Chỉ hiệu quả với CPU-bound code: I/O bound operations như đọc file, gọi API, query database sẽ không tăng tốc với Cython.
- Build environment khi deploy: Server cần có C compiler hoặc phải build sẵn wheel file. Công cụ
cibuildwheelgiúp tự động build cho nhiều platform. - Giữ Python version song song: Luôn giữ code Python thuần để debug và test logic — Cython chỉ là bản compile, không nên là bản duy nhất.
Cython không phải phép màu cho mọi vấn đề performance. Nhưng với những bài toán xử lý nặng có logic phức tạp, đây là công cụ thực dụng nhất để tăng tốc — không cần viết lại codebase, không cần học ngôn ngữ mới.

