Bốn mươi lăm phần, hàng trăm phép đo, một thông điệp duy nhất: đo, đừng đoán. Sê-ri này bắt đầu từ một niềm tin đơn giản mà sai — rằng hiệu năng có thể suy ra từ thông số phần cứng hay độ phức tạp Big-O. Từng phần một, phép đo phá tan niềm tin đó: cùng một thao tác, cùng một số phép tính, có thể nhanh chậm khác nhau chục đến nghìn lần tùy những chi tiết mà không cuốn sách thuật toán nào nhắc tới. Phần cuối này không đo cái gì mới — nó nhìn lại toàn cảnh, đúc kết những gì con số đã dạy, và mời bạn tiếp tục tự đo trên máy của mình.

Đo, đừng đoán

Hằng số ẩn chi phối, không phải Big-O

Bài học lớn nhất: hằng số ẩn của O(n) — thứ mà ký hiệu Big-O cố tình bỏ qua — mới là thứ chi phối thời gian chạy thật, và nó biến thiên khủng khiếp. Một vài con số tiêu biểu đã đo trên host ARM này:

Không con số nào trong số này đọc được từ thông số CPU hay đếm được bằng Big-O. Chúng chỉ hiện ra khi đo.

Mỗi máy một khác — đo trên máy đích

Bài học lớn thứ hai: không có con số vũ trụ. Máy tôi đo suốt sê-ri là một lõi ARM Apple Silicon (AArch64) chạy trong máy ảo Linux — và nó khác x86 ở những chỗ khiến nhiều "quy tắc tối ưu" quen thuộc trở nên thừa hoặc sai:

Chép một quy tắc tối ưu từ kiến trúc khác mà không đo có thể khiến bạn tối ưu cho một cỗ máy không tồn tại. Con số đúng chỉ đến từ máy đích của bạn.

Một lần tôi đo hớ: "đọc thông số là biết" và "đo một máy là biết mọi máy"

Xuyên suốt sê-ri, mỗi phần mở đầu bằng một đo hớ — một trực giác hợp lý bị phép đo bác bỏ. Ở bài kết này, hai đo hớ lớn nhất gói trọn tinh thần cả loạt bài.

Thứ nhất: "đọc thông số phần cứng, biết độ phức tạp Big-O, là đủ để biết code nhanh hay chậm". Sai — và sai hàng chục tới hàng nghìn lần. Hằng số ẩn (dữ liệu ở tầng cache nào, nhánh đoán được không, truy cập tuần tự hay ngẫu nhiên, có cắt ranh giới trang không) chi phối thời gian thật; hai đoạn code cùng O(n) có thể chênh nhau trăm lần. Không thể suy hiệu năng từ giấy — phải đo.

Thứ hai — cái bẫy tinh vi hơn: "được rồi, tôi đã đo trên máy tôi, vậy là biết". Nhưng đo trên một máy không cho biết mọi máy. ARM khác x86 (dòng 128 byte, không SMT/NUMA, atomic rẻ hơn, line-split miễn phí); một laptop khác một máy chủ; một máy ảo khác máy thật (như phần này thấy: VM giấu PMU, giấu NUMA). Con số tôi đo đúng cho máy này, nhưng bạn phải đo lại trên máy của bạn — vì đó mới là nơi code bạn chạy.

Bài học đo lường: ĐO, ĐỪNG ĐOÁN. Hằng số ẩn (cache, nhánh, tuần tự/ngẫu nhiên, căn lề, đồng bộ) chi phối thời gian thật hơn Big-O — cùng thao tác chênh chục-nghìn lần (đo: cache 113x, cột/hàng 17,9x, throw 1184x, page-split 29x, memcpy 15x). Mỗi máy MỘT khác (host ARM: dòng 128B, không SMT/NUMA, atomic rẻ, line-split free — quy tắc x86 thành thừa/sai). Phương pháp: container thật + SINK+warmup+min+đối chứng + báo trung thực khi không quan sát được. 'Đọc thông số/Big-O là biết hiệu năng' SAI (phải đo); 'đo một máy là biết mọi máy' SAI (phải đo máy đích). Nếu tin "đọc thông số là biết" tôi tối ưu mù; nếu tin "đo một máy là đủ" tôi mang số của máy khác áp lên máy mình.

Vì sao điều này quan trọng khi lập trình

Hệ quả đầu tiên: khi hiệu năng quan trọng, hãy viết một phép đo trước khi tin bất kỳ trực giác nào. Đừng tranh luận "cái này chắc nhanh hơn" — dựng một benchmark nhỏ (đúng cách: SINK, warmup, min, lặp-chia-N), chạy nó, đọc con số. Ba mươi giây đo thắng ba giờ suy đoán. Và khi con số khó tin (quá nhanh, quá chậm), nghi ngờ benchmark trước khi nghi ngờ phần cứng.

Hệ quả thứ hai: học vài hiện tượng vi kiến trúc cốt lõi — cache phân tầng, dự đoán nhánh, tuần tự vs ngẫu nhiên, chia sẻ dữ liệu giữa luồng — vì chúng giải thích phần lớn khác biệt hiệu năng bạn gặp. Không cần thuộc lòng mọi chi tiết; chỉ cần biết nghi ngờ đúng chỗ: code chậm bất thường mà số phép không đổi thì nghĩ tới cache và dự đoán trước tiên, rồi đo để xác nhận.

Hệ quả thứ ba, và là tinh thần của cả sê-ri: con số thắng trực giác, và con số phải đến từ máy đích của bạn. Mọi bài trong loạt này chỉ làm một việc — thay một câu "chắc là..." bằng một con số đo được, và gần như lần nào con số cũng bất ngờ. Đó là món quà của việc đo: nó liên tục cho bạn thấy thế giới không như bạn tưởng. Hãy giữ thói quen đó. Đừng tin trực giác hiệu năng của mình — kể cả sau khi đọc hết 45 phần này. Viết phép đo, chạy trên máy bạn, đọc con số. Đo, đừng đoán.

Thử ba mươi giây

Chọn một niềm tin về hiệu năng mà bạn đang mang — bất kỳ điều gì bạn tin là "nhanh hơn" hay "không đáng kể": duyệt mảng theo hàng hay cột, dùng std::vector hay std::list, một hàm ảo có đáng ngại không, memcpy hay vòng lặp tay, hằng số của thuật toán O(n log n) bạn vừa viết. Rồi đừng tin nó — viết mười lăm dòng benchmark (nhớ: ghi kết quả vào biến volatile, dùng dữ liệu khởi tạo lúc chạy, chạy nóng vài lần, lặp nhiều lần chia số phần tử, lấy giá trị nhỏ nhất) và đo nó trên chính máy bạn. Rất có thể con số sẽ làm bạn ngạc nhiên — nhanh hơn, chậm hơn, hoặc chênh nhiều lần so với bạn nghĩ. Đó là khoảnh khắc quan trọng nhất của cả sê-ri này: không phải một con số cụ thể tôi đã đo, mà là thói quen bạn vừa tập — thay một phỏng đoán bằng một phép đo. Làm nó một lần, bạn sẽ thấy nghiện; làm nó mãi, bạn sẽ viết code nhanh dựa trên sự thật thay vì niềm tin. Đo, đừng đoán — trên máy của bạn, hôm nay.