Kiến trúc máy tính: điều CPU thật sự làm
Đo chi phí thật của phần cứng CPU bằng C/C++ trong container Linux: dự đoán nhánh, đường ống, SIMD, phân tầng cache, TLB, ILP, atomic. Mỗi phần một phép đo thật, một lần đo hớ, một sơ đồ.
45/45 phần đã đăng
Giải thuật
1
Vì sao mảng đã sắp xếp từng chạy nhanh hơn (và giờ thì không)
Câu đố kinh điển trên Stack Overflow: cùng một vòng lọc, mảng đã sắp xếp chạy nhanh hơn hẳn mảng xáo trộn. Tôi dựng lại thí nghiệm trên máy ARM và thấy hai điều: hồi đó nó đúng, và bây giờ thì g++ đã âm thầm dẹp luôn cái hiệu ứng ấy.
03/09/2026
· 7 phút đọc
2
Một cú đoán sai nhánh đốt 12 chu kỳ: đo cái giá đó
Đoán sai một nhánh chỉ chậm đi một chút? Đo ra: quét tỉ lệ nhánh được lấy từ 0% đến 100%, thời gian vẽ thành một hình chuông đỉnh ở 50% — nhánh đoán được (0% hoặc 100%) tốn 0,24–0,45 ns còn nhánh 50/50 tốn 2,38 ns. Từ đó suy ra phạt mỗi lần đoán sai ~3,86 ns, tức khoảng 12 chu kỳ CPU — đúng cỡ một lần xả pipeline.
03/09/2026
· 9 phút đọc
3
Cùng số phép cộng, tách 8 biến ra nhanh gấp 6,7 lần
Cùng số phép tính thì cùng thời gian? Đo ra: cộng dồn một mảng bằng một biến tích lũy tốn 0,569 ns mỗi phép cộng, nhưng dùng 8 biến độc lập rồi gộp cuối chỉ tốn 0,085 ns — nhanh 6,7 lần dù y hệt số phép cộng. Khác biệt là đồ thị phụ thuộc: chuỗi phụ thuộc phải chờ từng bước, còn các phép độc lập chạy song song trong một lõi (ILP).
03/09/2026
· 8 phút đọc
4
SIMD nhanh gấp 4 lần — hoặc gấp 0, tùy vòng lặp
Trình biên dịch -O2 tự vector hóa hết mọi vòng lặp số? Đo ra: một kernel tính từng phần tử độc lập nhanh 3,9 lần khi vector hóa (NEON 128-bit gói 4 float mỗi lệnh), nhưng một vòng có phụ thuộc — y[i] cần y[i-1] — thì không vector hóa được, vector và scalar bằng nhau y hệt. SIMD nhanh N lần chỉ khi vòng vector hóa được và compute-bound.
03/09/2026
· 8 phút đọc
5
Cùng phép đọc bộ nhớ, khi 0,9 ns khi 103 ns: bậc thang cache
Một lần truy cập bộ nhớ tốn thời gian như nhau? Đo ra: đuổi con trỏ qua vùng làm việc lớn dần, thời gian mỗi truy cập vẽ thành một bậc thang — 0,91 ns khi dữ liệu ở L1, nhảy lên 3,5 ns qua L2, rồi 46 ns, rồi 103 ns khi ra RAM: chênh 113 lần theo tầng cache. Và các ngưỡng nhảy chính là kích thước từng tầng cache của máy.
03/09/2026
· 8 phút đọc
6
Đọc 1 byte, CPU nạp cả 128: cái giá của đọc thưa
Đọc một byte thì chỉ tốn một byte băng thông? Đo ra: CPU nạp cả một dòng cache mỗi lần chạm bộ nhớ, nên đọc thưa (mỗi phần tử một dòng) chậm gấp 10 lần đọc liền. Và ngưỡng bão hòa lộ kích thước dòng thật của máy: host ARM này (Apple Silicon) dùng dòng 128 byte, không phải 64 byte như x86 quen thuộc.
03/09/2026
· 8 phút đọc
7
8 con trỏ, vài chục byte, mà chậm gấp 4 lần: conflict miss
Dữ liệu nhỏ hơn cache thì luôn nằm trong cache? Đo ra: chỉ 8 con trỏ — vài chục byte dữ liệu thật — cũng chậm gấp 3-4 lần nếu chúng cách nhau đúng 64KB (lũy thừa 2), vì tất cả dồn vào cùng một tập cache và đá nhau ra (conflict miss). Cách nhau một số không lũy thừa 2 thì phẳng lì. Và ngưỡng conflict lộ luôn số way của cache.
03/09/2026
· 9 phút đọc
8
Cùng lượng cache, trải mỏng ra 1024 trang chậm 15 lần
Chỉ cache mới quyết định tốc độ truy cập bộ nhớ? Đo ra: cùng ~128KB dòng cache chạm y hệt nhau, nhưng dữ liệu trải rộng trên 1024 trang chậm gấp 15 lần so với gói gọn trong ~16 trang — 14 ns so với 0,9 ns. Thủ phạm là TLB: bộ nhớ đệm dịch địa chỉ ảo sang vật lý. Trải nhiều trang hơn TLB chứa được thì mỗi truy cập trả thêm một lần đi bảng trang.
03/09/2026
· 10 phút đọc
9
Hạ memory_order cho nhanh: mẹo x86 đem nhầm sang ARM
seq_cst đắt hơn relaxed nhiều, cứ hạ memory_order là code nhanh? Đo trên host ARM: relaxed và seq_cst chênh nhau đúng 0 ns — cả một luồng lẫn tám luồng — vì thứ tự nằm sẵn trong lệnh ldar/stlr/ldadd. Cái thật sự đắt là tính atomic (RMW gấp 7 lần ghi thường) và nhất là tranh chấp: tám luồng đập chung một atomic chậm gấp 60 lần tám ô riêng.
03/09/2026
· 10 phút đọc
10
Một lệnh mỗi chu kỳ? Lõi này nuốt 7,5 lệnh
CPU chạy một lệnh mỗi chu kỳ, thời gian tỉ lệ số lệnh? Đo trên host ARM: một chuỗi phép cộng phụ thuộc chạy đúng 1 lệnh mỗi chu kỳ, nhưng cho lõi mười chuỗi độc lập thì nó nuốt tới ~7,5 phép cộng mỗi chu kỳ — cùng một lõi, cùng loại lệnh. Thêm việc độc lập gần như miễn phí cho tới khi lấp hết cổng thực thi, rồi bão hòa. IPC vượt xa 1.
03/09/2026
· 9 phút đọc
11
Lõi CPU tám cổng ngồi chơi 474/475 thời gian: vì sao?
Lõi CPU rộng 8 thì cái gì cũng nhanh? Đo trên host ARM: một chuỗi phép phụ thuộc — mỗi phép chờ kết quả phép trước — chạy đúng tốc độ độ trễ, và cái lõi tám cổng ấy hoàn toàn vô dụng. Độ trễ mỗi phép chênh nhau khủng khiếp: cộng 1 chu kỳ, nhân 3, chia 7,5, nạp từ RAM theo con trỏ tới 475 chu kỳ. Đường tới hạn là trần cứng không lõi nào phá được.
03/09/2026
· 8 phút đọc
12
Bung vòng lặp 8 lần mà không nhanh hơn một tí nào?
Bung vòng lặp (unroll) luôn làm code nhanh hơn vì cắt chi phí vòng? Đo trên host ARM: bung một vòng cộng mảng 1x/2x/4x/8x với một biến tích lũy cho thời gian y hệt nhau — 0,2136 ns mỗi phần tử, không đổi, vì lõi out-of-order đã giấu hết chi phí vòng. Cái thật sự giúp là phá chuỗi phụ thuộc bằng nhiều biến độc lập (3,5 lần), còn khi memory-bound thì unroll vô ích. Và -O3 tự vector hóa còn nhanh hơn bung tay.
03/09/2026
· 8 phút đọc
13
Căn lề dữ liệu đã lỗi thời? Một cú SIGBUS nói không
Căn lề là chuyện cũ, CPU nay đọc lệch thoải mái? Đo trên host ARM: đọc lệch mà vẫn nằm trong một dòng cache thì nhanh y hệt căn lề — hoàn toàn miễn phí. Nhưng bắc cầu hai dòng cache thì tốn: L1 chậm thêm 33%, RAM chậm gấp 2,2 lần vì phải nạp hai dòng. Và một atomic vắt qua ranh giới dòng thì không chậm — nó Bus Error, chương trình chết ngay.
03/09/2026
· 8 phút đọc
14
RAM chậm trăm ns, sao vòng lặp vẫn chạy vèo vèo?
Chạm bộ nhớ RAM thì luôn tốn cả trăm nanô giây? Đo trên host ARM: duyệt một mảng 256 MB theo thứ tự tuần tự chỉ tốn 0,24 ns mỗi phần tử — kể cả duyệt lùi cũng vậy — vì bộ nạp trước đoán và kéo dòng cache về trước. Cùng mảng ấy, truy cập ngẫu nhiên tốn 101 ns, chênh 420 lần. Bộ nạp trước theo kịp cả bước đều rất lớn, chỉ chịu thua kiểu ngẫu nhiên.
03/09/2026
· 9 phút đọc
15
100 cú chạm RAM mà chỉ tốn bằng 4 cú: phép màu MLP
Mỗi lần chạm hụt cache phải chờ xong mới tới lần sau, nên N lần miss tốn N lần độ trễ? Đo trên host ARM: đuổi một con trỏ ngẫu nhiên tốn 102 ns mỗi bước, nhưng đuổi 32 con trỏ độc lập song song chỉ còn 3,9 ns mỗi bước — nhanh 26 lần. Lõi thực thi ngoài thứ tự cho hàng chục lần chạm hụt bay cùng lúc. Cùng là truy cập RAM ngẫu nhiên, phụ thuộc hay độc lập chênh nhau 26 lần.
03/09/2026
· 8 phút đọc
16
Dấu chia tốn 8 chu kỳ hay gần như 0? Tùy bạn viết
Nhân với chia đều là một phép toán nên tốn như nhau? Đo trên host ARM: chuỗi phép nhân số thực chạy 3,3 chu kỳ mỗi phép, nhưng chia số thực tốn 10,7 chu kỳ — gấp hơn ba lần, và gấp mười lần một phép cộng. Số nguyên udiv 8 chu kỳ so với mul 3,2. Nhưng chia cho hằng số thì trình biên dịch biến thành nhân, và lấy dư cho lũy thừa 2 thành phép AND — gần như miễn phí.
03/09/2026
· 8 phút đọc
17
Cùng phép nhân, khi 3,2 chu kỳ khi 0,4: sao lại thế?
Mỗi lệnh CPU có một chi phí cố định? Đo trên host ARM: cùng một phép nhân tốn 3,2 chu kỳ khi nằm trong chuỗi phụ thuộc, nhưng chỉ 0,4 chu kỳ khi các phép độc lập — chênh 8 lần. Mỗi lệnh có HAI con số: độ trễ và thông lượng. Bất ngờ hơn: cả phép chia số thực cũng chồng được ~8 lần, dù tôi tưởng bộ chia làm từng cái một.
03/09/2026
· 8 phút đọc
18
Đếm bit 1: vòng lặp 32 nhịp hay một lệnh CPU?
Đếm số bit 1 phải lặp qua từng bit? Đo trên host ARM: đếm bit bằng vòng 32 lần tốn 7,83 ns mỗi số, nhưng lệnh phần cứng __builtin_popcount chỉ 0,25 ns — nhanh 31 lần, và còn nhanh gấp đôi cả mẹo tay Kernighan lẫn bảng tra. Bất ngờ hơn: trình biên dịch không tự nhận ra vòng đếm bit để thay bằng lệnh CNT — phải gọi builtin mới có.
03/09/2026
· 7 phút đọc
19
Bảng tra sẵn: mẹo tối ưu kinh điển đã hết thời?
Tính sẵn kết quả vào bảng tra luôn nhanh hơn tính lại lúc chạy? Đo trên host ARM: một hàm băm 4 phép tính lại chỉ tốn 0,330 ns, còn tra bảng thì tùy kích thước — bảng 4KB trong L1 nhanh 0,284 ns, nhưng bảng 1MB (L2) chậm 1,5 lần và bảng 64MB (RAM) chậm 7,6 lần so với tính lại. Trên CPU hiện đại, chạm bộ nhớ xa đắt hơn tính vài phép rẻ — nên châm ngôn cũ đã lật.
03/09/2026
· 7 phút đọc
20
Thêm phép tính vào vòng lặp mà không chậm hơn: vì sao?
Vòng lặp duyệt mảng lớn nhanh chậm do số phép tính? Đo trên host ARM bằng STREAM: copy (không phép tính), add, triad (nhân+cộng) đều đạt ~65-92 GB/s như nhau — nút cổ chai là băng thông bộ nhớ, không phải phép tính. Và một luồng đã đạt ~85% đỉnh, hai luồng bão hòa ~95 GB/s: trên lõi Apple này một core gần như dùng hết băng thông, khác hẳn server x86.
03/09/2026
· 8 phút đọc
21
Cùng một mảng, chạm cách này nhanh hơn cách kia 424 lần
Truy cập bộ nhớ có một tốc độ cố định? Đo trên host ARM cùng một mảng 256MB, cùng số lần chạm: duyệt tuần tự tốn 0,24 ns mỗi phần tử (33 GB/s), nhưng đuổi con trỏ ngẫu nhiên tốn 101,7 ns — chênh 424 lần. Và ngẫu nhiên độc lập (nhờ song song mức bộ nhớ) chỉ 3,3 ns, nhanh 31 lần đuổi con trỏ. Ba trần khác nhau, chọn bởi cách bạn truy cập.
03/09/2026
· 8 phút đọc
22
Mỗi luồng một biến riêng mà vẫn chậm gấp 2,6 lần?
Mỗi luồng ghi biến riêng của nó thì chạy song song thoải mái? Đo trên host ARM: tám luồng, mỗi luồng tăng một bộ đếm riêng, nhưng nếu tám bộ đếm nằm chung một dòng cache thì chậm gấp 2,6 lần so với khi mỗi bộ đếm được đệm ra dòng riêng — dù các biến hoàn toàn độc lập về logic. Đây là false sharing: dòng cache ping-pong giữa các lõi. Ghi thường không atomic vẫn trả giá.
03/09/2026
· 8 phút đọc
23
Atomic đắt gấp mấy lần? Từ 6,5 tới 74, tùy đông người
Phép atomic có một chi phí cố định, đắt hơn lệnh thường mấy lần? Đo trên host ARM: một luồng, atomic tốn 1,59 ns so với lệnh thường 0,24 ns — gấp 6,5 lần. Nhưng tám luồng cùng đập vào một atomic thì mỗi phép ngốn 18 ns — gấp 74 lần. Còn atomic riêng mỗi luồng (đệm ra dòng cache riêng) chỉ 0,25 ns, scale gần hoàn hảo. Cái đắt là tranh chấp, không phải bản thân atomic.
03/09/2026
· 7 phút đọc
24
Bỏ if cho nhanh: mẹo hay hay giáo điều?
Code không nhánh (branchless) luôn nhanh hơn if? Đo trên host ARM: khi nhánh khó đoán (dữ liệu ngẫu nhiên), branchless nhanh gấp 3 lần vì né được dự đoán sai. Nhưng khi nhánh dễ đoán và thân điều kiện rất đắt (8 phép chia, hiếm khi chạy), bản có if lại thắng vì bỏ qua được việc đắt. Và nhiều khi trình biên dịch đã tự bỏ nhánh giúp bạn rồi mà không hay.
03/09/2026
· 8 phút đọc
25
Lệnh không nhánh có luôn thắng if? Còn tùy đồng xu
Lệnh chọn không nhánh (cmov/csel) luôn tốt hơn một if? Đo trên host ARM, quét theo xác suất điều kiện đúng: chi phí của nhánh là một đường cong hình chuông — gần như miễn phí khi điều kiện luôn đúng hoặc luôn sai (dễ đoán), nhưng đỉnh 0,58 ns khi 50/50 (khó đoán nhất). Còn branchless thì phẳng lì ~0,37 ns. Hai đường giao nhau quanh 15% và 85% — và đó mới là chỗ quyết định.
03/09/2026
· 7 phút đọc
26
Cái 'bộ đếm chu kỳ' của bạn không đếm chu kỳ đâu
Đọc bộ đếm chu kỳ của CPU là đếm được số chu kỳ? Đo trên host ARM: bộ đếm timer cntvct chạy tần số cố định 24 MHz, còn CPU chạy ~4,44 GHz — nên một tick bằng 41,67 ns, tức khoảng 185 chu kỳ CPU chứ không phải một. Và mỗi lần gọi steady_clock::now() tốn ~13 ns; đo đoạn ngắn hơn thế thì kết quả toàn là rác. Đây là cách đo cho đúng.
03/09/2026
· 8 phút đọc
27
Đổi int64 thành int8, vòng lặp nhanh gấp 10 lần
Kiểu số nào cũng 'một phép tính' nên tốc độ như nhau? Đo trên host ARM: cùng một phép trên mảng, khi vector hóa thì int8 nhanh gấp 10 lần int64 — thanh ghi NEON 128-bit gói 16 số int8 nhưng chỉ 2 số int64 mỗi lệnh. Dữ liệu lớn (memory-bound) kiểu hẹp cũng thắng ~9 lần vì ít byte. Nhưng ở chế độ vô hướng mọi kiểu int lại y hệt, và kiểu nhỏ dễ tràn — nên quy tắc là hẹp nhất đủ dùng.
03/09/2026
· 8 phút đọc
28
Một dòng trộn int với float, và 19 chu kỳ bốc hơi
Gán số nguyên cho biến float trông như miễn phí — 'đều là số' mà. Đo trên host ARM: một phép cộng int tốn ~1 chu kỳ, nhưng vòng chuyển khứ hồi (int)((float)v+1) trên chuỗi phụ thuộc tốn 4,3 ns — khoảng 19 chu kỳ, gấp 19 lần. int và float là hai thế giới riêng trong CPU, và các phép ép kiểu ngầm trong biểu thức trộn kích hoạt lệnh chuyển đổi mà bạn không hề viết ra.
03/09/2026
· 8 phút đọc
29
Đổi chỗ hai dòng for, code chạy chậm đi 18 lần
Duyệt ma trận theo hàng hay theo cột — cùng số phần tử, cùng phép cộng, chỉ khác thứ tự — thì như nhau chứ? Đo trên host ARM: ma trận 64MB duyệt theo hàng 0,211 ns mỗi phần tử, theo cột 3,79 ns — chậm gần 18 lần. Mỗi lần chạm cột rơi vào một dòng cache riêng, CPU nạp 128 byte nhưng chỉ dùng 4, phí 31/32 dòng và cộng cả lỗi TLB. Thứ quyết định không phải số phần tử, mà số byte dòng cache phải kéo.
03/09/2026
· 8 phút đọc
30
Benchmark chạy trên mảng nhỏ đang nói dối bạn
Microbenchmark báo 0,9 ns mỗi phần tử, lên production chậm gấp trăm lần — code không sai, dữ liệu đã đổi cỡ. Đo trên host ARM: cùng một thao tác O(n) đuổi con trỏ, 0,91 ns khi vừa L1, 104 ns khi ra RAM — chênh 114 lần. Vẽ ns/phần tử theo working set ra một đường bậc thang, và 'O(n) là tuyến tính' hóa ra chỉ đúng trong một tầng cache.
03/09/2026
· 7 phút đọc
31
Ghi một biến rồi đọc lại ngay: CPU có bắt bạn chờ không?
Trực giác bảo lệnh đọc phải đợi lệnh ghi xuống cache. Đo trên host ARM thì không: store buffer chuyền thẳng giá trị vừa ghi, ghi-đọc khớp chỉ 1,55 ns (~7 chu kỳ). Nhưng cái 'store-forwarding stall' mà dân x86 sợ chết khiếp, ở đây chỉ đắt thêm 18%. Thứ thật sự chi phối lại là chuyện khác.
03/09/2026
· 10 phút đọc
32
Máy 10 nhân chạy 10 luồng có nhanh gấp 10 lần không? Đo ra: 6,4
Thấy 10 CPU thì tưởng chạy 10 luồng nhanh gấp 10 lần. Nhưng đo ra chỉ đạt đỉnh 6,4 lần, rồi thêm luồng nữa còn phẳng lì. Hai lý do: '10 CPU' đôi khi là ảo (SMT gộp hai luồng logic một lõi), và ngay cả lõi vật lý thật cũng không đồng đều — có nhân khỏe nhân yếu.
03/09/2026
· 5 phút đọc
33
Đo hiệu năng: vì sao phải lấy min chứ không phải trung bình
Bấm giờ một lần rồi tin luôn con số? Cùng một đoạn code, 200 lần đo cho con số thấp nhất 23,85 ms nhưng có lần vọt lên 30,50 — cao hơn 28%. Nhiễu chỉ làm phép đo chậm đi chứ không bao giờ nhanh hơn khả năng thật của lõi, nên số nhỏ nhất mới đáng tin, còn trung bình thì bị kéo lệch lên.
03/09/2026
· 6 phút đọc
34
Chia code thành nhiều hàm nhỏ có làm chương trình chậm đi không?
Nửa dân lập trình sợ gọi hàm tốn kém nên gộp code lại; nửa kia force-inline mọi thứ. Đo ra một cú gọi hàm chỉ tốn chừng 0,45 nano-giây, và với hàm có việc thật thì con số đó gần như tàng hình. Quan trọng hơn: trình biên dịch đã tự inline hộ bạn đúng chỗ đáng inline rồi.
03/09/2026
· 5 phút đọc
35
Con trỏ hàm, callback, std::function: chậm hay không tùy CPU có đoán nổi đích
Con trỏ hàm với std::function bị mang tiếng là chậm vì gọi gián tiếp. Nhưng đo ra, khi đích ổn định — kể cả một mẫu lặp đều đặn — nó nhanh gần bằng gọi thẳng. Chỉ khi đích nhảy loạn khó đoán mới chậm gấp 6 lần. Cái quyết định không phải bản thân cú gọi gián tiếp, mà là CPU có đoán nổi bạn sắp gọi ai.
03/09/2026
· 5 phút đọc
36
Hàm ảo chậm hay không, hóa ra tùy một điều bạn ít để ý
Nhiều người né hàm ảo ở đường nóng vì nghe nói nó tốn hai lần đọc bộ nhớ cộng một cú gọi gián tiếp. Nhưng đo ra, cùng một dòng obj->f(), lúc thì nhanh y như gọi thường, lúc lại chậm gần 7 lần — và cái quyết định không phải bản thân virtual, mà là các đối tượng bạn duyệt có cùng kiểu hay trộn loạn.
03/09/2026
· 5 phút đọc
37
try/catch không làm code bạn chậm đi — nhưng một cú throw thì đắt gấp 1184 lần
Nửa dân C++ tránh exception vì sợ try/catch làm chậm, nửa kia ném vô tội vạ. Đo ra cả hai đều hiểu sai: bọc try/catch mà không ném thì tốn đúng bằng không, còn ném thật một phát tốn tận 800 nano-giây. Chi phí không nằm ở chỗ họ tưởng.
03/09/2026
· 6 phút đọc
38
memcpy nhanh gấp 15 lần cái vòng for bạn tự viết — và tốc độ nó không phải một con số
Nhìn thì memcpy chỉ là chép một dãy byte, viết tay bằng vòng for cũng ra thế. Nhưng đo ra memcpy nhanh gấp 15 lần cái vòng đó, và tốc độ của nó còn nhảy múa gấp 40 lần tùy khối to hay nhỏ. Một hàm tưởng tầm thường mà giấu cả một đường cong hiệu năng.
03/09/2026
· 5 phút đọc
39
calloc 512 MB trong 0,003 mili-giây — hệ điều hành giấu cái giá đi đâu?
calloc một mảng nửa gig lẽ ra phải mất thời gian ghi hết 512 triệu số 0. Nhưng nó xong trong 0,003 mili-giây, nhanh hơn ghi thật cỡ 4300 lần. Không phải phép màu — hệ điều hành lười một cách tài tình, và cái giá thật thì nó khéo léo dời sang chỗ khác.
03/09/2026
· 5 phút đọc
40
Căn lề dữ liệu: nỗi sợ của dân x86 mà ARM gần như chẳng thèm quan tâm
Ai cũng dặn phải căn lề dữ liệu kẻo truy cập bộ nhớ chậm. Nhưng đo trên ARM, đọc lệch — kể cả cắt ngang dòng cache — nhanh y hệt căn lề. Chỉ có đúng một chỗ khiến nó lết: khi 8 byte vắt qua ranh giới trang, chậm gần 30 lần. Cái x86 bắt bạn sợ với cái thật sự nguy hiểm hóa ra không phải một.
03/09/2026
· 5 phút đọc
41
Đổi hai dòng vòng lặp, nhân ma trận nhanh gấp 3 mà không thêm phép tính nào
Cùng một phép nhân ma trận, cùng đúng 2,1 tỷ phép nhân-cộng, tôi chỉ hoán vị hai vòng lặp mà tốc độ nhảy từ 2,65 lên 8,13 GFLOP/s. Không phải phép màu — là cache. Big-O đếm số phép, nhưng cách bạn đi qua dữ liệu mới là thứ quyết định code chạy nhanh hay lết.
03/09/2026
· 6 phút đọc
42
NUMA: cái tối ưu mà phần lớn máy của bạn chẳng cần tới
Ai đọc về hiệu năng cũng gặp NUMA: RAM chia thành node, đọc node của mình thì nhanh, node khác thì chậm. Nghe ghê gớm, nhưng máy tôi đo lại chẳng có NUMA nào cả — 10 lõi đọc RAM đều tăm tắp, chênh nhau vỏn vẹn 6%. Và đó là chuyện của đa số laptop, VM lẫn máy ARM.
03/09/2026
· 6 phút đọc
43
Khi 'perf' báo Operation not permitted mà vẫn đo được branch-miss
Người ta bảo muốn biết cache-miss hay branch-miss thật thì phải có perf và bộ đếm phần cứng. Máy tôi đo thì perf còn chẳng chạy — VM khóa luôn PMU. Vậy mà chỉ với một cái đồng hồ và một thí nghiệm đổi đúng một biến, branch-miss vẫn hiện ra rõ mồn một, chậm gần 7 lần.
03/09/2026
· 5 phút đọc
44
Benchmark của bạn đang nói dối: bốn cách bạn tự lừa mình khi đo tốc độ
Viết một vòng lặp, bọc đồng hồ hai đầu, chia cho số phần tử — nghe là xong. Nhưng cùng một đoạn code, tôi đo được 0 nano-giây (nhanh vô hạn), rồi 0,57, rồi 2,62 ns, tùy vào những cái bẫy mà trình biên dịch giăng sau lưng. Chỉ một cách đo là ra con số thật.
03/09/2026
· 6 phút đọc
45
Đo, đừng đoán — điều duy nhất tôi muốn bạn giữ lại
Khép lại sê-ri 45 bài đo kiến trúc máy tính. Nếu quên hết mọi con số, chỉ cần nhớ một câu: đừng tin trực giác về hiệu năng, hãy viết một phép đo và chạy nó trên chính máy mình. Vì cùng một thao tác có thể nhanh chậm khác nhau chục đến nghìn lần, và mỗi cỗ máy lại một khác.
03/09/2026
· 6 phút đọc