Trình biên dịch tối ưu code của bạn thế nào

Sê-ri đo đạc: mổ xẻ các tối ưu của trình biên dịch bằng thực nghiệm trong container gcc dùng một lần — đo mã máy sinh ra và tốc độ thật, so kỳ vọng với thực tế.

45/45 phần đã đăng Giải thuật
1 -O0 chậm hơn -O2 gần 5 lần, còn -O3 gần như không hơn -O2 — cả hai điều tôi tưởng đều sai Cùng một code C, -O0 chậm 4,6 lần -O2 — không phải vì làm nhiều phép tính hơn, mà vì giữ mọi biến trên ngăn xếp (72 lệnh load/store so với 26 khi lên -O1). Cú nhảy tốc độ lớn nhất nằm ở -O0 → -O1; còn -O2 và -O3 cho mã gần như y hệt. Số cao hơn không đảm bảo nhanh hơn. 03/09/2026 · 8 phút đọc 2 Inline để bỏ lệnh gọi hàm ư? Tôi đo: bỏ call chỉ được 0,15 ns — lợi thật gấp mười lần thế, ở chỗ khác Ai cũng bảo inline để khỏi tốn chi phí gọi hàm. Tôi đo và thấy bỏ một lệnh call chỉ tiết kiệm 0,15 ns — call trên CPU hiện đại gần như miễn phí. Lợi thật là tối ưu xuyên biên: gọi g(x,0) hằng số, inline giúp gấp hằng và khử nhánh chết, nhanh 1,8 lần. Nhưng inline hàm lớn vào 24 chỗ làm mã phình gấp đôi. 03/09/2026 · 8 phút đọc 3 Vòng lặp 2 tỉ phép tính của tôi đo ra đúng 0 nano giây — vì gcc xóa sạch nó, và đó là bài học nền Một vòng 2 tỉ phép tính mà kết quả không ai dùng đo ra đúng 0,00 ns — gcc -O2 xóa cả vòng, hàm chỉ còn ret. Thêm một volatile sink là số nhảy lên 1,37 ns thật. Khử mã chết còn xóa dead store và mã sau return, và đây chính là lý do mọi benchmark cần 'quan sát' kết quả. 03/09/2026 · 8 phút đọc 4 Một hàm ba phép nhân, gcc rút thành đúng một con số nạp sẵn — nếu tôi cho nó biết đầu vào Ai cũng biết compiler tính sẵn 2+3 thành 5. Tôi đo xem nó đi xa tới đâu: cùng một hàm ba phép nhân, khi đầu vào là hằng 100 thì gcc gấp toàn bộ thành một literal 64-bit (5 lệnh, 0 phép nhân, 0 ns); khi đầu vào từ volatile thì phải tính đủ (22 lệnh, 3 phép nhân, 0,99 ns). Ranh giới nằm ở một câu hỏi. 03/09/2026 · 8 phút đọc 5 "Phải -O3 mới có vector hóa" — niềm tin này đã sai từ gcc 12, và tôi đo để chứng minh Ai cũng bảo muốn compiler tự vector hóa SIMD thì phải bật -O3. Tôi đo trên gcc 13: -O2 đã sinh 26 lệnh NEON và nhanh gấp 2 lần -O1 scalar, còn -O3 cho mã y hệt. Và cách biết chắc vòng nào được vector hóa không phải đoán theo mức -O, mà là đọc -fopt-info-vec — compiler tự khai. 03/09/2026 · 7 phút đọc 6 Bật -funroll-loops phình mã gấp 2,6 lần mà không nhanh hơn một giây — lợi thật của bung vòng nằm ở chỗ khác Bung vòng để 'giảm nhánh' là lời giải thích bỏ lỡ điều quan trọng. Tôi đo: bung với nhiều biến tích lũy độc lập nhanh 3,72 lần vì phá chuỗi phụ thuộc, nhưng bung cơ học của -funroll-loops phình mã 121→314 lệnh mà 0 tăng tốc — và bung x8 còn chậm hơn x4. Cái đáng làm không phải chép thân vòng. 03/09/2026 · 8 phút đọc 7 Vì sao bung vòng x8 lại chậm hơn x4? Tôi đếm lệnh và tìm ra thủ phạm: CPU hết thanh ghi CPU chỉ có ~28 thanh ghi số nguyên dùng được trên AArch64; vượt ngưỡng đó, biến sống cùng lúc bị tràn ra ngăn xếp, và lệnh load/store trong vòng nóng leo từ 1 (4 biến) lên 103 (48 biến). Đây chính là lời giải cho bí ẩn bung vòng x8 chậm hơn x4 — và vì sao biến cục bộ không hề miễn phí. 03/09/2026 · 8 phút đọc 8 Cùng một hàm, cùng đầu vào, hai kết quả: 1 với -O2 và 1073741824 với một cờ — dấu vân tay của UB Đọc lại bit của một int như float bằng ép kiểu con trỏ nghe vô hại. Tôi đo: cùng hàm trả về 1 với -O2 nhưng 1073741824 với -fno-strict-aliasing — một cờ đổi kết quả là bằng chứng chắc chắn của hành vi không xác định. Strict aliasing cho tốc độ ~9%, nhưng type-punning ép kiểu vi phạm nó; memcpy mới đúng, mà vẫn nhanh. 03/09/2026 · 8 phút đọc 9 Bật -ffast-math: tổng 20 triệu số chạy nhanh gấp 4 — nhưng ra một con số khác -ffast-math nghe như nút tăng tốc miễn phí cho code toán. Tôi đo: cùng tổng 20 triệu float, -O3 cho 1999531776 trong 0,571 ns/phần tử, còn -ffast-math cho 1999546496 trong 0,143 ns — nhanh 4 lần nhưng KHÁC kết quả, vì nó sắp lại phép cộng để vector hóa mà số thực máy không kết hợp. Tốc độ đổi lấy độ chính xác. 03/09/2026 · 8 phút đọc 10 Hàm kiểm tràn tôi viết bị gcc rút thành 'luôn trả về 0' — vì tôi vô tình hứa với nó rằng tràn không xảy ra Hành vi không xác định (UB) chỉ là 'crash hoặc giá trị rác'? Tôi đo và thấy nó là một lời hứa compiler tin tuyệt đối, rồi xóa chính những kiểm tra an toàn tôi viết: if(p==NULL) sau khi deref, if(a+100<a) kiểm tràn — cả hai biến mất ở -O2, để lỗi lọt qua im lặng. Cái tối ưu UB mở khóa nằm ở mã, không phải ở đồng hồ. 03/09/2026 · 10 phút đọc 11 Tưởng -O2 đã vắt kiệt, thêm mỗi cờ -flto mà nhanh 1,59 lần — vì nó nhìn thấy thứ -O2 không thấy -O2 tưởng là trần tối ưu cho production? Tôi đo và thấy nó chỉ tối ưu trong PHẠM VI MỘT FILE — ranh giới file chặn nội tuyến xuyên hàm. Bật -flto phá ranh giới đó: hàm hot ở file khác được nội tuyến, lệnh gọi biến mất khỏi vòng nóng, nhanh 1,59 lần chỉ nhờ thêm một cờ. 03/09/2026 · 9 phút đọc 12 Tôi cố tình cho PGO một hồ sơ SAI ngược thực tế để nó phản đòn — nó vẫn nhanh y như hồ sơ đúng PGO tối ưu theo hồ sơ chạy thật thay vì đoán tĩnh. Tôi đoán hồ sơ sai (thu trên dữ liệu lệch ngược) sẽ làm nó chậm hơn cả -O2 vì tối ưu nhầm nhánh — đo ra không hề: với một nhánh trong vòng nóng, PGO vẫn nhanh y hệt, vì cái nó thắng là bố cục mã, không phải hướng nhánh mà CPU đã tự đoán. 03/09/2026 · 8 phút đọc 13 Tôi đi săn ca -O3 chậm hơn -O2 để chứng minh 'số cao hơn chưa chắc tốt' — và không bắt được con nào Định trưng ra một ca -O3 thua -O2, tôi đo bốn kernel trên gcc 13/ARM và không ca nào -O3 chậm hơn — nó thắng đậm hoặc hòa. Nhưng cái giá thật vẫn đo được, chỉ ở chỗ khác: hàm phình từ 15 lên 47 lệnh. -O3 chắc chắn thêm mã, chỉ CÓ THỂ thêm tốc độ. 03/09/2026 · 8 phút đọc 14 Mẹo "nhân 8 thì viết x<<3 cho nhanh" — tôi đo trên ARM đời nay và nó gần như vô nghĩa Thay nhân bằng dịch bit luôn nhanh hơn rõ, chia hằng thành magic luôn thắng? Tôi đo và cả hai đều tùy: trên ARM hiện đại nhân đã rẻ ngang cộng nên dịch chỉ nhanh hơn tí, còn chia-hằng chỉ thắng ở thông lượng — trong chuỗi phụ thuộc nó còn chậm hơn cả phép chia thật. Compiler tự làm hết, đừng tự tay. 03/09/2026 · 8 phút đọc 15 Lưu x*y vào biến tạm cho nhanh? Compiler đã làm rồi — cái đáng lưu là chỗ khác Ai cũng được dạy lưu biểu thức chung vào biến tạm cho khỏi tính lại. Tôi đo và thấy compiler tự làm: bản viết lặp và bản lưu tạm sinh mã giống hệt. Nhưng một phép ghi qua con trỏ có thể trùng lại chặn đứng nó, buộc tính lại — và restrict khôi phục, nhanh 1,34 lần ở phép chia. Aliasing mới là thứ quyết định. 03/09/2026 · 8 phút đọc 16 Vòng lặp for(i<strlen(s)) — huyền thoại O(n²): compiler cứu, cho tới khi tôi thêm một phép ghi Compiler luôn kéo mọi thứ bất biến ra khỏi vòng? Đúng với số học thuần, và nó còn hoist cả strlen khi thân chỉ đọc. Nhưng một phép ghi vào chuỗi trong thân chặn đứng nó: for(i<strlen(s)) sửa chuỗi thành O(n²) thật — n=32.000 chậm 990 lần. Biên giới của tối ưu là aliasing. 03/09/2026 · 7 phút đọc 17 Code không nhánh luôn nhanh hơn? Tôi đo: thắng 5,3 lần khi khó đoán, nhưng THUA khi dễ đoán Ai cũng khuyên viết branchless (csel/cmov) cho nhanh. Tôi đo cả một đường cong theo độ đoán được và thấy nó chỉ đúng một nửa: nhánh 50/50 khó đoán thì không-nhánh nhanh 5,3 lần, nhưng nhánh 99% dễ đoán thì có-nhánh lại nhanh hơn — vì nhánh dễ đoán gần như miễn phí và còn được bỏ qua việc. 03/09/2026 · 8 phút đọc 18 switch luôn O(1) nhanh hơn if-else? Tôi đo và niềm tin đó sai ở ba chỗ khác nhau Ai cũng nghe switch được compiler biến thành bảng nhảy O(1) nên nhanh hơn if-else. Tôi đo và thấy nó sai nhiều mặt: switch chỉ thành bảng khi case dày, bảng nhảy có chi phí nhảy gián tiếp khó đoán nên thua if-else khi case nóng ở đầu, và 12 phép so sánh gần bằng 1 khi nhánh đoán được. 03/09/2026 · 8 phút đọc 19 Đọc lệch lề crash hoặc chậm gấp đôi? Trên ARM đời nay tôi đo ra gần như miễn phí Ai cũng bảo dữ liệu lệch lề thì crash hoặc chậm thảm họa. Tôi đo trên ARM AArch64 và thấy gần như vô hại: đọc lệch bằng căn lề, vắt cache line chỉ chậm 1%, atomic lệch không crash. Bất ngờ ngược: padding struct để 'căn lề cho nhanh' lại làm CHẬM vì mảng phình gây nhiều cache miss. 03/09/2026 · 7 phút đọc 20 Thêm một hàm không bao giờ chạy, benchmark của tôi đổi 4% — thủ phạm là căn lề mã Căn lề mã cho tốc độ ổn định, cứ tăng -falign-loops là tốt? Tôi đo và thấy tác động nhỏ (~2%) và thất thường trên ARM. Quan trọng hơn: căn lề mã chính là nguồn nhiễu benchmark khét tiếng — giữ hot loop y hệt, chỉ chèn một hàm chết để dịch địa chỉ, thời gian đổi ~4%, còn lớn hơn cả việc cố ý chỉnh căn lề. 03/09/2026 · 7 phút đọc 21 restrict để vector hóa? gcc -O3 tự lo mà không cần — và khai sai một chữ là kết quả sai lặng lẽ Không có restrict thì không vector hóa được, luôn chậm? Tôi đo và thấy gcc -O3 tự chèn kiểm tra alias lúc chạy rồi vector hóa cả khi thiếu restrict. restrict chỉ giúp vòng nhỏ gọi nhiều (~20%), không giúp vòng lớn. Và nó là lời hứa compiler tin tuyệt đối — khai sai khi con trỏ thật sự trùng thì kết quả sai, không một lời cảnh báo. 03/09/2026 · 8 phút đọc 22 Đảo byte tôi tự viết bằng shift/mask nhanh ngang builtin — vì gcc nhận ra tôi đang làm gì Phải dùng builtin/intrinsics mới nhanh, viết C tay luôn chậm? Tôi đo và thấy đúng nửa: builtin thắng rõ ở phép C không có cách viết tự nhiên (popcount nhanh 1,8 lần), nhưng nhiều phép compiler tự nhận mẫu — đảo byte viết tay bằng shift/mask cũng ra đúng một lệnh rev, bằng builtin. Đo trước khi cho rằng viết tay là chậm. 03/09/2026 · 7 phút đọc 23 likely/unlikely: mách đúng cho compiler được ~0, mách sai mất 10% — một canh bạc lỗ Thêm likely/unlikely tưởng tăng tốc nhánh rõ rệt. Tôi đo và thấy một canh bạc bất đối xứng xấu: mách đúng thì lợi gần như bằng không (bộ dự đoán nhánh động của CPU đã lo hướng), nhưng mách sai làm chậm 10%. Cái nó đổi chỉ là bố cục mã, không phải hướng đoán — nên hãy để PGO lo, đừng rắc theo cảm tính. 03/09/2026 · 8 phút đọc 24 Ép always_inline cho hàm nóng để chắc nhanh? Tôi đo: nó thừa — kẻ thật sự đổi cuộc chơi là noinline Ai cũng khuyên ép always_inline cho hàm nóng, và sợ nội tuyến hàm to làm phình mã chậm. Tôi đo và cả hai đều lệch: always_inline hàm nhỏ là thừa (compiler đã inline), hàm to vẫn nhanh 2 lần dù mã phình 3,2 lần. Kẻ có tác động thật là noinline — cấm inline làm chậm 2,3 lần vì chặn cả vector hóa. 03/09/2026 · 8 phút đọc 25 Đổi thứ tự 6 trường trong struct: 40 byte xuống 24, và mảng duyệt nhanh 1,6 lần Sắp trường struct thế nào cũng vậy, compiler lo? Tôi đo và thấy chính thứ tự bạn khai quyết định kích thước: cùng 6 trường, thứ tự xấu phình 40 byte vì padding, thứ tự tốt chỉ 24 — và mảng 20 triệu phần tử duyệt nhanh 1,6 lần chỉ vì gọn hơn. Compiler bị cấm tự đổi thứ tự, nên đây là tối ưu miễn phí chỉ bạn làm được. 03/09/2026 · 8 phút đọc 26 Tôi malloc một struct rồi free — gcc xóa sạch cả hai, làm như tôi chưa từng viết Khai struct local là chắc chắn tốn ô nhớ stack, malloc thì tốn heap? Tôi đo và thấy compiler làm biến mất cả hai khi chứng minh được đối tượng không thoát khỏi hàm: struct thành thanh ghi, malloc/free bốc hơi, tốc độ y hệt. Cái đắt không phải việc khai hay cấp phát — mà là lấy địa chỉ (&) khiến nó thoát ra ngoài. 03/09/2026 · 8 phút đọc 27 Hàm ảo "luôn chậm nên tránh"? Tôi đo: cùng lời gọi đó chênh nhau 6,6 lần tùy dữ liệu Ai cũng bảo tránh virtual cho nhanh. Tôi đo và thấy nó phụ thuộc một thứ lời khuyên đó bỏ qua: khi một điểm gọi chỉ thấy một kiểu (monomorphic) thì gọi ảo gần bằng gọi trực tiếp — 0,722 ns; chỉ khi nhiều kiểu trộn khó đoán (megamorphic) mới chậm 6,6 lần. Cái đắt là đích nhảy khó đoán, không phải từ khóa virtual. 03/09/2026 · 8 phút đọc 28 -march=native nhanh 2,66 lần — rồi làm vỡ build ngay trước mắt tôi Cứ bật -march=native cho nhanh? Tôi đo và thấy hai mặt: -march đúng mở khóa lệnh dotprod cho kernel int8 nhanh 2,66 lần thật, nhưng -march=native trong container này còn không build nổi vì assembler từ chối lệnh gcc phát ra. Rủi ro mong manh của nó hiện ngay lúc build, chưa nói tới SIGILL lúc chạy trên máy khác. 03/09/2026 · 7 phút đọc 29 Chia cho 7 gần như miễn phí, chia cho một biến đắt gấp 2,7 lần — cùng một dấu gạch chéo Chia là chia, tốn như nhau? Tôi đo và thấy cùng dấu / cho hai chi phí khác hẳn: chia cho hằng số biến thành nhân magic + dịch (vector hóa được, gần như miễn phí), còn chia cho biến là udiv đắt 2,7 lần. Nhưng trong chuỗi phụ thuộc, cái mẹo magic lại không thắng — lợi ở thông lượng, không ở độ trễ. 03/09/2026 · 8 phút đọc 30 -Os cho binary nhỏ nhất mà tôi tưởng phải chậm — hóa ra có kernel nó chỉ thua -O2 9% -Os luôn chậm hơn -O2 vì bỏ tối ưu? Tôi đo cả kích thước lẫn tốc độ và cái nhãn chậm chỉ đúng nửa: -Os cho mã nhỏ nhất trong nhóm tối ưu, và tốc độ thì hỗn hợp — kernel số học nặng chậm 66% vì mất vector, nhưng kernel nhiều nhánh chỉ chậm 9%. Nó là điểm đánh đổi khác, không phải bản chậm. 03/09/2026 · 7 phút đọc 31 Prefetch bằng tay — sách bảo cứu truy cập ngẫu nhiên, máy tôi lại nói ngược sạch __builtin_prefetch nghe như nút tăng tốc cho code nghẽn bộ nhớ, nhất là truy cập ngẫu nhiên và con trỏ đuổi. Tôi đo trên máy này và mọi thứ lật ngược: prefetch giúp truy cập tuần tự (~1,5 lần), vô ích cho gather ngẫu nhiên, và bất lực với con trỏ đuổi. Một bài học về việc prefetch phụ thuộc phần cứng đến mức nào. 03/09/2026 · 7 phút đọc 32 Compiler âm thầm dời khối xử-lý-lỗi ra cuối hàm — nhưng đồng hồ của tôi gần như không nhúc nhích Tách đường nóng khỏi đường lạnh nghe như tăng tốc rõ. Tôi đo: compiler đã tự đẩy khối unlikely xuống cuối hàm, thân vòng nóng sạch bong không một lệnh lạnh — nhưng thời gian chỉ đổi ~1%, chìm trong nhiễu. Vì đây là tối ưu bố cục i-cache, chỉ hiện lợi ở quy mô lớn, không ở một vòng nhỏ. 03/09/2026 · 7 phút đọc 33 Gộp hai vòng làm một tưởng nhanh hơn — tôi đo, nó chậm đi 2,75 lần Ghép hai vòng thành một để bớt overhead thì luôn nhanh hơn? Tôi đo và bất ngờ: ghép hai vòng cùng đọc một mảng nhanh 1,8 lần, nhưng ghép hai vòng mỗi vòng ghi một mảng lại chậm 2,75 lần. Thứ quyết định không phải số vòng, mà là số luồng bộ nhớ đan xen trong thân — một bài toán locality. 03/09/2026 · 7 phút đọc 34 Hai chuỗi "abc" viết ở hai hàm khác nhau lại cùng một địa chỉ — và vì sao đừng mừng vội Tưởng mỗi literal là một vùng nhớ riêng? Tôi đo: compiler gộp các literal trùng thành một bản duy nhất trong .rodata, nên con trỏ tới chúng bằng nhau và binary nhẹ đi. Nhưng chuẩn C không hề bảo đảm điều đó — dựa vào == để so chuỗi là một bug chờ nổ, luôn dùng strcmp. 03/09/2026 · 8 phút đọc 35 Đặt điều kiện rẻ trước để né phép đắt — mẹo kinh điển này lại là bản CHẬM NHẤT khi tôi đo Ai cũng bảo đặt điều kiện rẻ, hay-sai lên trước để && dừng sớm né phép đắt thì nhanh hơn. Tôi đo và hiệu năng đi ngược: bản bitwise & không nhánh, tính cả hai mọi lần, lại nhanh nhất. Hóa ra giá trị thật của && không phải tốc độ, mà là ngữ nghĩa bảo vệ null — thứ mà đổi sang & là làm crash. 03/09/2026 · 8 phút đọc 36 volatile không làm biến nhanh hơn — nó làm code chậm 5 lần, và tôi vẫn cần nó Nhiều người tưởng volatile cho nhanh hoặc cho an toàn đa luồng. Tôi đo thì ngược hẳn: volatile chặn tối ưu nên chậm 5,1 lần, vì mỗi lần chạm biến là một chuyến bộ nhớ thật. Nó đúng cho MMIO, cờ ngắt, và biến sink khi đo — nhưng nó KHÔNG phải atomic và không hề an toàn đa luồng. 03/09/2026 · 7 phút đọc 37 Rào bộ nhớ sinh 0 lệnh máy mà vẫn làm code chậm 5,8 lần — cái đắt nằm ở đâu? Rào bộ nhớ là một lệnh CPU đắt tiền luôn có mặt? Tôi đo và bất ngờ: rào biên dịch sinh 0 lệnh máy mà vẫn chậm 5,8 lần vì nó chặn tối ưu, còn rào phần cứng có lệnh dmb thật lại nhanh hơn. Cái giá thật là tối ưu bị tước đi, không phải bản thân lệnh rào — và nó vô hình trong assembly. 03/09/2026 · 9 phút đọc 38 Vòng chép byte tự viết của tôi chậm hơn memcpy 25 lần — thủ phạm là một chữ tôi quên memcpy là lời gọi thư viện, và vòng chép byte tay nhanh ngang nó? Tôi đo và sai cả hai. memcpy kích thước hằng được inline thẳng thành lệnh vector, 0 lời gọi; còn vòng chép byte tay chậm 25 lần vì compiler sợ hai con trỏ trùng địa chỉ nên không dám tối ưu. Thêm restrict là nó nhanh bằng memcpy ngay. 03/09/2026 · 8 phút đọc 39 gcc 'biết' hàm này luôn chia cho 7 — mà ở -O2 vẫn không thèm tối ưu, vì sao? Tôi tưởng compiler tự chuyên biệt bất kỳ hàm nào gọi với hằng số. Đo lần đầu ở -O2 ra 1,0× — chẳng lợi gì. Hóa ra bản clone chuyên biệt chỉ bật ở -O3: khi đó hằng được nướng vào, phép chia hóa thành nhân ma thuật, nhanh 1,3 lần. Một tối ưu có trong compiler khác hẳn với nó đang chạy. 03/09/2026 · 7 phút đọc 40 Đổi chỗ hai dòng for, code chậm đi 16,9 lần — và -O3 chỉ cứu số nguyên, không cứu float Cùng một phép cộng ma trận, đổi thứ tự hai vòng lồng là chênh 16,9 lần vì cache. Tôi tưởng compiler chịu thua chuyện này. Đo ra -O3 tự đảo thứ tự cứu số nguyên (về 1,0×), nhưng với float thì bó tay — vì đảo thứ tự cộng làm đổi kết quả làm tròn. Ranh giới nằm ở một chỗ rất tinh vi. 03/09/2026 · 8 phút đọc 41 Cùng một hàm constexpr: một chỗ chạy 0,227 ns, chỗ kia 45625 ns — khác nhau ở đâu? Đánh dấu constexpr là chắc chắn tính sẵn lúc biên dịch, 0 chi phí? Tôi đo và phát hiện chữ đó hứa ít hơn tưởng. Đặt kết quả vào biến constexpr thì nó thành literal đọc 0,227 ns; gọi đúng hàm ấy với đầu vào động thì nó chạy nguyên vòng lặp lúc chạy, 45625 ns. Ngữ cảnh mới quyết định, không phải từ khóa. 03/09/2026 · 8 phút đọc 42 File C của tôi có 6 lỗi thật mà gcc mặc định im bặt: bật cờ nào bắt được mấy? Code biên dịch không cảnh báo chưa chắc là code đúng — tôi viết một file C gài sẵn 6 lỗi rồi đo. Mặc định bắt 0/6, -Wall -Wextra bắt 4 nhưng sót rò rỉ và null-deref, chỉ -fanalyzer mới thấy hai lỗi đó. Cú lật cuối: -fanalyzer ở -O2 lại sót chính cái rò rỉ nó bắt ở -O0. 03/09/2026 · 8 phút đọc 43 Vòng cộng 1..100 biến mất, chia thành phép nhân: ba lần tôi đoán sai mã máy chỉ trong ba hàm Đọc nguồn C rồi đoán compiler làm gì — tôi đoán ba hàm, sai cả ba. Vòng cộng 1..100 gập thẳng thành một hằng số 5050, x/7 hóa thành phép nhân ma thuật, cộng mảng thì -O2 chạy scalar còn -O3 vector hóa. Cách đọc assembly để biết chắc, không phải đoán. 03/09/2026 · 9 phút đọc 44 Bấm giờ một cái cờ rồi tin luôn: cách đo ngây thơ cho con số sai 25.000 lần Tưởng bật/tắt cờ rồi bấm giờ một lần là biết nó đáng giá? Tôi đo kiểu ngây thơ (vứt kết quả): ra 0,041 ns vì khử mã chết xóa cả vòng — sai ~25.000 lần và báo scalar bằng vector. Đo đúng cách (volatile sink + warmup + min) mới lộ sự thật: vector hóa ở workload này thực ra CHẬM 1,18 lần. Phương pháp quyết định con số có nói thật. 03/09/2026 · 9 phút đọc 45 Bật hết -O3 -Ofast -march=native cho chắc? Đo ra chỉ một cờ nhanh thật — và nó đổi kết quả Tưởng cứ -O3 -Ofast -march=native -funroll là nhanh nhất? Tôi đo một workload thật: -O3 bằng -O2, -march không đổi, -funroll chỉ 3%, chỉ -ffast-math nhanh 2,9 lần nhưng đổi kết quả số thực. Không có combo cờ thần — mỗi cờ là một đánh đổi phụ thuộc workload, và cách duy nhất chọn đúng là đo ở đúng máy, đúng cờ, đúng code của bạn. 03/09/2026 · 9 phút đọc