Đừng đoán, hãy đo — profiling, benchmark và định luật Amdahl
Vì sao trực giác về hiệu năng thường sai, cách profiling tìm đúng điểm nóng, các bẫy benchmark (warmup, JIT), và định luật Amdahl giới hạn lợi ích tối ưu.
TL;DR: Lập trình viên thường tối ưu sai chỗ vì trực giác về hiệu năng hay bị cache, branch prediction và I/O đánh lừa. Profiling (sampling hoặc instrumentation) chỉ ra hotspot thực — phần nhỏ code chiếm phần lớn thời gian. Benchmark đúng cách phải tránh ba bẫy: chưa warmup JIT, compiler xoá dead code, đo quá ngắn. Định luật Amdahl giới hạn lợi ích: nếu đoạn code chỉ chiếm 5% thời gian tổng, tối ưu hoàn hảo đoạn đó cũng chỉ cải thiện tổng thể chưa đến 5%. Quy trình đúng: đo trước, tìm hotspot, tối ưu hotspot, đo lại, dừng khi đủ.
Một lập trình viên bỏ cả ngày tối ưu hàm formatDate() — viết lại từ đầu, thêm cache, giảm allocation. Kết quả: nhanh hơn 8 lần. Nghe rất ấn tượng.
Nhưng sau khi deploy, không ai nhận ra sự khác biệt. Profiler chỉ ra formatDate() chỉ chiếm 1.2% tổng thời gian xử lý request. Điểm nóng thực sự nằm ở query database không có index, chiếm 78% — và không ai nhìn vào đó.
Câu chuyện này lặp lại hằng ngày trong các team. Bài này giải thích vì sao trực giác hiệu năng hay sai, cách dùng profiler tìm hotspot thực, những bẫy làm benchmark cho ra số giả, và định luật Amdahl đặt giới hạn lý thuyết cho mọi nỗ lực tối ưu — cốt lõi của phương pháp luận đo-trước-tối-ưu-sau.
1. Analogy — bác sĩ chẩn đoán trước khi mổ
Một bác sĩ giỏi không mổ ngay khi bệnh nhân kêu đau bụng. Bác sĩ đặt câu hỏi, siêu âm, xét nghiệm máu, chụp CT — rồi mới chỉ đúng chỗ cần can thiệp. Mổ bừa không chỉ vô ích mà còn gây hại.
Profiler là máy chẩn đoán của lập trình viên. Nó đo chính xác phần nào của chương trình đang tiêu tốn tài nguyên, giúp bạn "mổ" đúng chỗ thay vì đoán mò.
| Bác sĩ | Lập trình viên |
|---|---|
| Bệnh nhân kêu "đau" | App chạy chậm, user phàn nàn |
| Siêu âm, xét nghiệm, chụp CT | Profiling, benchmark, flame graph |
| Chẩn đoán chính xác điểm bệnh | Xác định hotspot (hàm/vòng lặp tốn nhất) |
| Phẫu thuật có chỉ định | Tối ưu đúng chỗ, đúng mức |
| Kiểm tra sau điều trị | Benchmark lại sau tối ưu |
Profiling = chẩn đoán. Optimization = phẫu thuật. Không chẩn đoán thì không mổ.
2. Vì sao trực giác hiệu năng thường sai
Lập trình viên thường đoán sai hotspot vì não người không được thiết kế để mô phỏng CPU hiện đại.
Cache ẩn chi phí thực. Truy cập RAM chậm hơn L1 cache đến khoảng 200 lần (cache sẽ được đào sâu ở Course 2 — Bộ nhớ). Một vòng lặp trông vô hại lại phá cache line → toàn bộ thời gian nằm ở chờ bộ nhớ, không phải ở phép tính. Mắt người chỉ thấy số lần lặp, không thấy cache miss.
Branch prediction ảnh hưởng không lường. Bài 03 — Branch prediction giải thích: khi CPU dự đoán sai nhánh, pipeline flush tốn 10-20 cycle. Với vòng lặp 10 triệu lần, mỗi lần miss có thể tốn nhiều hơn cả phép tính bên trong. Không đo thì không thể biết branch có "nhảy loạn" không.
I/O che khuất tất cả. Đọc file, query database, gọi network API — những thứ này chậm hơn tính toán CPU hàng nghìn lần. Một hàm calculateScore() nhanh đến mấy cũng không cứu được nếu gọi nó mười lần trong một vòng lặp đang chờ database.
Hệ quả: tối ưu bằng linh cảm thường trúng sai chỗ. Quy tắc 90/10 (còn gọi là nguyên lý Pareto trong hiệu năng) cho thấy thực nghiệm: khoảng 10% code chịu trách nhiệm cho khoảng 90% thời gian chạy. Profiler tìm đúng 10% đó.
3. Profiling — tìm hotspot bằng dữ liệu
Profiling là kỹ thuật thu thập dữ liệu về runtime để xác định đoạn code nào tiêu tốn nhiều tài nguyên nhất (CPU time, memory, I/O).
3.1 Hai chiến lược profiling
Sampling profiler hoạt động như sau: cứ mỗi một khoảng thời gian nhất định (thường 1-10ms), nó dừng chương trình lại và ghi nhận call stack hiện tại. Sau hàng nghìn lần lấy mẫu, stack nào xuất hiện nhiều nhất thì hàm đó đang tiêu CPU nhiều nhất.
Ưu điểm: overhead thấp (thường dưới 5%), có thể chạy trên production. Nhược điểm: bỏ sót hàm chạy rất nhanh nhưng gọi rất thường xuyên nếu khoảng lấy mẫu quá dài.
Instrumentation profiler chèn code đo thời gian vào mọi hàm — ghi nhận chính xác số lần gọi và tổng thời gian mỗi hàm. Chính xác hơn, nhưng bản thân việc đo đã làm chương trình chậm lại đáng kể (thường 2-10 lần). Chỉ dùng trong môi trường dev/staging, không dùng production.
flowchart LR
A["Chuong trinh chay"] --> B{"Profiler loai"}
B -->|"Sampling"| C["Lay mau call stack\nmoi 1-10ms"]
B -->|"Instrumentation"| D["Chen do thoi gian\nvao moi ham"]
C --> E["Flame graph\n(anh xep chong call stack)"]
D --> F["Bang thoi gian\nchính xac tung ham"]
E --> G["Tim hotspot"]
F --> G3.2 Công cụ theo hệ sinh thái
| Ngôn ngữ / Platform | Sampling | Instrumentation |
|---|---|---|
| Linux (bất kỳ ngôn ngữ nào) | perf (Linux perf_events) | — |
| Java (JVM) | async-profiler, Java Flight Recorder (JFR) | YourKit, JProfiler |
| Python | py-spy (zero-overhead, production-safe) | cProfile (stdlib) |
| Browser / JavaScript | Chrome DevTools Performance tab | — |
| Go | pprof (tích hợp sẵn) | — |
Với Java, async-profiler là lựa chọn phổ biến: attach vào JVM đang chạy, xuất flame graph SVG, không cần restart. Nếu bạn đang theo dõi track java-internals, JFR (Java Flight Recorder) tích hợp sẵn trong JDK 11+ và thu thập cả GC, thread contention, I/O — rất phù hợp để phân tích toàn diện.
Flame graph là cách trực quan hoá call stack: trục ngang là tỉ lệ CPU time, trục dọc là độ sâu call stack. Thanh rộng nhất ở đỉnh = hàm tốn nhiều CPU nhất. Đọc từ dưới lên: thanh dưới cùng gọi thanh phía trên. Tìm thanh rộng ở đỉnh — đó là hotspot. Brendan Gregg (Netflix) là người phát minh flame graph và công bố open-source tại github.com/brendangregg/FlameGraph.
4. Benchmark đúng cách — và những bẫy phổ biến
Profiling tìm hotspot; benchmark đo chính xác hiệu năng của một đoạn code cụ thể. Nhưng benchmark sai sẽ cho số vô nghĩa — thậm chí ngược chiều thực tế.
4.1 Bẫy 1: Chưa warmup JIT
JVM (và các runtime có JIT như V8, CLR) không compile bytecode thành native code ngay từ đầu. JIT chờ một hàm được gọi đủ nhiều lần (HotSpot JVM mặc định 10.000 lần) rồi mới compile và tối ưu. Đo ngay từ lần gọi đầu tiên tức là đo thời gian interpret, không phải thời gian JIT-compiled.
// SAI: do ngay tu vong lap dau -> JIT chua kick in
long start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
result = compute(data);
}
long elapsed = System.nanoTime() - start;
System.out.println("Time: " + elapsed + " ns");
Vài nghìn lần đầu tiên chạy interpret (chậm hơn 10-100 lần native). Kết quả đo bị kéo lên cao và không phản ánh hiệu năng thực tế của ứng dụng sau khi warm.
Giải pháp: warmup trước, đo sau.
// DUNG: warmup truoc (it nhat 10.000 lan), do sau
for (int i = 0; i < 20_000; i++) {
compute(data); // warmup - JIT compile
}
// Gio moi do
long start = System.nanoTime();
for (int i = 0; i < 10_000; i++) {
result = compute(data);
}
long elapsed = System.nanoTime() - start;
4.2 Bẫy 2: Dead-code elimination
Compiler (hoặc JIT) rất thông minh. Nếu kết quả tính toán không được dùng đến đâu, compiler xoá luôn vòng lặp — benchmark đo thời gian của... không có gì.
// SAI: JIT co the xoa toan bo vong lap nay
// vi ket qua sum khong duoc su dung
long sum = 0;
for (int i = 0; i < 1_000_000; i++) {
sum += data[i];
}
// sum bi bỏ -> compiler remove dead code -> thoi gian = 0ns
Kết quả benchmark ra 0 nanosecond — nghe tuyệt vời nhưng hoàn toàn vô nghĩa.
Giải pháp: dùng kết quả theo cách nào đó (print ra, return về, accumulate vào biến global).
// DUNG: dam bao ket qua duoc "consume"
long sum = 0;
for (int i = 0; i < 1_000_000; i++) {
sum += data[i];
}
blackhole.consume(sum); // JMH BlackHole, hoac System.out.println(sum)
4.3 Bẫy 3: Đo quá ngắn và nhiễu hệ thống
Đo một lần hoặc đo trong thời gian ngắn (dưới 100ms) cho số không đáng tin do:
- OS scheduling jitter: OS có thể preempt thread giữa chừng, tạo ra độ trễ ngẫu nhiên hàng chục ms.
- GC pause: garbage collector có thể chạy giữa benchmark, làm tăng thời gian đột ngột.
- CPU frequency scaling: CPU hiện đại có turbo boost, tần số thay đổi theo nhiệt độ và tải.
- Cache cold start: lần đầu chạy data chưa trong cache.
Giải pháp: lặp đủ nhiều lần (ít nhất vài giây tổng), lấy trung bình hoặc trung vị (median), bỏ qua các outlier.
4.4 Công cụ benchmark chuẩn: JMH cho Java
Viết benchmark tay đúng cách rất khó. JMH (Java Microbenchmark Harness, phát triển bởi team OpenJDK) xử lý tự động warmup, dead-code prevention, fork process riêng để tránh nhiễu, và báo cáo với confidence interval.
// Benchmark voi JMH
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 10, time = 1)
@Fork(2)
public class StringBenchmark {
@Benchmark
public String concatenateLoop(Blackhole bh) {
String s = "";
for (int i = 0; i < 100; i++) {
s += i; // potentially slow: creates many String objects
}
return s;
}
@Benchmark
public String concatenateBuilder(Blackhole bh) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append(i);
}
return sb.toString();
}
}
JMH tự quản lý warmup iterations, tách quá trình đo vào fork riêng, và xuất kết quả dạng ops/s hoặc ns/op kèm độ lệch chuẩn.
5. Định luật Amdahl — giới hạn lý thuyết của mọi tối ưu
Giả sử bạn đã profiling và tìm ra hotspot thực sự. Câu hỏi tiếp theo: tối ưu đoạn đó xong, tổng thể nhanh hơn bao nhiêu?
Định luật Amdahl (Gene Amdahl, 1967) trả lời câu hỏi này với công thức:
Speedup_total = 1 / ((1 - p) + p/k)
Trong đó:
plà tỉ lệ thời gian của phần được tối ưu (từ 0 đến 1)klà hệ số tăng tốc của phần đó (lớn hơn 1)(1 - p)là phần còn lại không được tối ưu, không đổi
5.1 Ví dụ số cụ thể
Chương trình chạy 100 giây. Profiling chỉ ra hàm A chiếm 30% thời gian (30 giây). Bạn viết lại hàm A, nó nhanh hơn 10 lần.
Tính speedup tổng:
p = 0.30,k = 10- Thời gian mới =
(1 - 0.30) * 100 + 0.30/10 * 100 = 70 + 3 = 73 giây - Speedup =
100 / 73 ≈ 1.37 lần— chỉ nhanh hơn 37%, không phải 10 lần
Hoặc dùng công thức: 1 / (0.70 + 0.30/10) = 1 / 0.73 ≈ 1.37.
Bây giờ thay đoạn code đầu bài: hotspot thực chiếm 78%, hàm formatDate() chiếm 1.2%.
| Tình huống | p | k | Speedup tổng |
|---|---|---|---|
Tối ưu formatDate() 8 lần | 0.012 | 8 | 1 / (0.988 + 0.012/8) ≈ 1.01 — tăng 1% |
| Tối ưu query DB 5 lần | 0.78 | 5 | 1 / (0.22 + 0.78/5) ≈ 3.1 — nhanh gấp 3 |
| Tối ưu query DB 10 lần | 0.78 | 10 | 1 / (0.22 + 0.78/10) ≈ 3.6 — nhanh gấp 3.6 |
Ngay cả tối ưu query 10 lần cũng bị chặn ở 3.6 vì (1 - p) = 0.22 là trần không thể phá vỡ.
5.2 Trần lý thuyết khi k tiến vô cùng
Khi k → ∞ (tối ưu hoàn hảo, phần đó không tốn thời gian nữa):
Speedup_max = 1 / (1 - p)
Nếu p = 0.80 thì Speedup_max = 1 / 0.20 = 5 — dù tối ưu phần đó đến mức không tốn một nanosecond, toàn bộ chương trình chỉ nhanh hơn 5 lần tối đa.
flowchart LR A["Tong thoi gian = 100s"] --> B["Phan co the toi uu\np = 80%\n80 giay"] A --> C["Phan con lai\n1-p = 20%\n20 giay"] B -->|"k = 4 lan"| D["80/4 = 20 giay"] C --> E["Van la 20 giay"] D --> F["Tong moi: 40 giay\nSpeedup = 2.5x"] E --> F B -->|"k = vo cung"| G["gan 0 giay"] G --> H["Tong moi: ~20 giay\nSpeedup max = 5x"] E --> H
Hàm ý thực tế: trước khi bắt đầu tối ưu, hỏi: "Phần này chiếm bao nhiêu phần trăm thời gian tổng?" Nếu câu trả lời dưới 10%, dừng lại — Amdahl đã chứng minh không đáng công sức.
6. Đào sâu (tuỳ chọn)
Microbenchmark vs macrobenchmark: Microbenchmark (như JMH) đo một hàm riêng lẻ trong điều kiện kiểm soát. Macrobenchmark đo cả luồng request end-to-end (load test với k6, Gatling, wrk). Microbenchmark chính xác nhưng có thể không phản ánh behaviour thực vì JIT tối ưu khác khi code cô lập. Macrobenchmark gần thực tế hơn nhưng khó isolate nguyên nhân. Dùng cả hai: microbenchmark để so sánh hai implementation cụ thể, macrobenchmark để xác nhận cải thiện thực sự có ý nghĩa với user.
Observability ở production — đo thật khác đo lab: Lab benchmark chạy trên máy dev sạch, không có traffic thật, không có GC pressure thật, không có network jitter thật. Production observability (distributed tracing với Jaeger/Zipkin, APM như Datadog/New Relic, custom metrics với Prometheus) đo điều kiện thực. Thông thường hotspot production khác hotspot đo trong lab vì data shape, concurrency pattern, và cache warmth khác nhau hoàn toàn. Kết hợp: profiling trên production traffic là mục tiêu lý tưởng — async-profiler và JFR có thể làm điều này với overhead thấp.
Amdahl mở rộng cho song song:
Định luật Amdahl ban đầu đặt câu hỏi về tốc độ tối ưu một phần. Trong ngữ cảnh multicore (teaser cho module tiếp theo về CPU hiện đại multicore), Amdahl cũng đặt trần cho song song hoá: thêm N core không cho speedup N lần nếu phần serial (1-p) tồn tại. Với p = 0.95 (95% code có thể song song), speedup tối đa với vô hạn core là 1 / 0.05 = 20 lần — không phải vô hạn.
Gustafson's Law — góc nhìn bổ sung:
Amdahl giả định kích thước bài toán cố định. John Gustafson (1988) lập luận: thực tế người dùng thường tăng kích thước bài toán khi có thêm core. Với bài toán lớn hơn, phần song song hoá chiếm tỉ lệ lớn hơn → speedup không bị chặn cứng như Amdahl. Gustafson's Law: Speedup = N - (1-p)(N-1). Hai luật bổ trợ nhau: Amdahl cho bài toán cố định, Gustafson cho bài toán co giãn theo tài nguyên.
7. Áp dụng vào code của bạn
Phương pháp đo-trước-tối-ưu-sau được tóm lại trong quy trình năm bước:
ĐO (profiling) → TÌM HOTSPOT → TỐI ƯU HOTSPOT → ĐO LẠI → DỪNG KHI ĐỦ
Mỗi bước có nguyên tắc cụ thể:
Bước 1 — ĐO: chạy profiler trên workload đại diện (không phải synthetic). Với Java: async-profiler hoặc JFR. Với Python: py-spy. Với browser: Chrome DevTools. Đừng bắt đầu tối ưu trước bước này.
Bước 2 — TÌM HOTSPOT: đọc flame graph hoặc report. Tập trung vào 1-3 hàm/section chiếm nhiều % nhất. Áp Amdahl ngay: hàm chiếm dưới 5% thời gian → bỏ qua, không tối ưu.
Bước 3 — TỐI ƯU HOTSPOT: sau khi xác định hotspot, áp các kỹ thuật phù hợp (thuật toán tốt hơn, tránh allocation, cải thiện locality, thêm index vào DB...). Thay đổi từng thứ một, không thay nhiều thứ cùng lúc (không biết cái gì tạo ra khác biệt).
Bước 4 — ĐO LẠI: chạy cùng profiling/benchmark sau tối ưu. So sánh số. Nếu không có số trước thì không biết đã cải thiện bao nhiêu.
Bước 5 — DỪNG KHI ĐỦ: "đủ" nghĩa là đã đạt target hiệu năng (latency P99 dưới 200ms, throughput đủ tải peak). Không tiếp tục tối ưu vô thời hạn — chi phí maintain code phức tạp hơn phải được justify bởi cải thiện đo được.
Ưu tiên tối ưu dựa trên Amdahl:
| Hotspot chiếm | Tối ưu 10 lần → speedup tổng | Đáng làm? |
|---|---|---|
| Dưới 5% | Dưới 1.05 lần | Không |
| 20% | Khoảng 1.22 lần | Cân nhắc |
| 50% | Khoảng 1.82 lần | Có |
| 80% | Khoảng 3.57 lần | Ưu tiên cao |
| 95% | Khoảng 6.9 lần | Ưu tiên rất cao |
Về "premature optimization": Donald Knuth viết năm 1974: "Premature optimization is the root of all evil." Câu này thường bị trích sai ngữ cảnh — Knuth không nói đừng bao giờ tối ưu. Câu đầy đủ là: "We should forget about small efficiencies, say about 97% of the time. Yet we should not pass up our opportunities in that critical 3%." Ý là: đừng tối ưu sớm khi chưa biết đâu là 3% quan trọng — hãy profiling trước để tìm ra.
Nhưng "đừng tối ưu sớm" không có nghĩa là viết code cố tình chậm. Viết code rõ ràng và dùng cấu trúc dữ liệu đúng từ đầu — đó là kỹ năng cơ bản, không phải tối ưu sớm. Sau đó profiling sẽ chỉ ra nơi cần đầu tư thêm.
Quy trình này sẽ là nền tảng cho bài 07 — Capstone: đo và tối ưu vòng lặp nóng — bạn sẽ áp dụng toàn bộ pipeline: profiling → Amdahl estimate → tối ưu → benchmark before/after.
8. Liên hệ các bài khác
- Bài 03 — Branch prediction: branch khó đoán là một lý do điển hình khiến hiệu năng khó đoán bằng mắt — phải đo. Bài này đã nhắc inline ở section 2 về ảnh hưởng của branch miss, đọc bài 03 để hiểu cơ chế pipeline flush bên dưới.
- Bài 02 — Pipeline và hazard: stall do hazard cũng là chi phí ẩn chỉ đo mới thấy. Profiler ở tầng CPU (như
perf stat) có thể lộ pipeline stall cycle — biết hazard là gì giúp đọc số này có nghĩa. - Bài 07 — Capstone: tối ưu vòng lặp nóng: áp dụng trực tiếp quy trình đo lường (profiling → Amdahl → tối ưu → benchmark) của bài này. Section 7 đã preview bài capstone — đây là nơi thực hành toàn bộ pipeline.
9. Tóm tắt
- Trực giác hiệu năng thường sai vì cache, branch prediction và I/O ảnh hưởng theo cách không trực quan — phải đo, không đoán.
- Quy tắc 90/10: khoảng 10% code chiếm khoảng 90% thời gian chạy. Profiler tìm đúng 10% đó.
- Sampling profiler (async-profiler, py-spy, perf) overhead thấp, dùng được production. Instrumentation profiler chính xác hơn nhưng chỉ dùng trong dev/staging.
- Ba bẫy benchmark phổ biến: chưa warmup JIT, compiler xoá dead code (dead-code elimination), và đo quá ngắn với nhiễu hệ thống — JMH (Java) xử lý cả ba.
- Định luật Amdahl:
Speedup = 1 / ((1-p) + p/k). Hotspot chiếm tỉ lệ nhỏ → tối ưu hoàn hảo cũng cho lợi ích nhỏ. Tính Amdahl trước khi bắt đầu tối ưu. - Quy trình đúng: ĐO → tìm hotspot → tối ưu hotspot → ĐO LẠI → dừng khi đủ. Viết code rõ ràng trước, profiler sẽ chỉ điểm cần tối ưu.
- "Premature optimization is the root of all evil" (Knuth) — nhưng câu đầy đủ nhắc: đừng bỏ qua 3% quan trọng. Profiling giúp xác định đúng 3% đó.
10. Tự kiểm tra
Q1Một lập trình viên tối ưu hàm sort() từ O(n²) xuống O(n log n), nhanh hơn 50 lần. Profiling cho thấy hàm này chiếm 2% thời gian tổng. Theo định luật Amdahl, speedup tổng thể là bao nhiêu? Kết luận gì?▸
1 / ((1 - 0.02) + 0.02/50) = 1 / (0.98 + 0.0004) ≈ 1.02 — tổng thể chỉ nhanh hơn 2%. Dù sort nhanh hơn 50 lần, Amdahl chặn lợi ích tổng ở mức tỉ lệ hotspot. Kết luận: thời gian tối ưu sort không xứng với lợi ích thu được. Lẽ ra phải profiling trước, tìm hotspot chiếm phần lớn thời gian, và tối ưu chỗ đó.Q2Vì sao benchmark JVM không warmup thường cho kết quả sai lệch? JIT làm gì khác nhau giữa giai đoạn đầu và sau khi warm?▸
Q3Dead-code elimination trong benchmark là gì? Cho ví dụ và cách phòng tránh.▸
sum là dead code và xoá. Benchmark ra 0 nanosecond, hoàn toàn vô nghĩa. Cách phòng: dùng JMH Blackhole.consume(result) hoặc đảm bảo kết quả được return/print để compiler không thể chứng minh nó unused.Q4Sampling profiler và instrumentation profiler khác nhau thế nào? Trường hợp nào dùng cái nào?▸
Q5Vì sao phải đo lại sau khi tối ưu, không chỉ tin vào lý luận lý thuyết rằng thay đổi phải làm nhanh hơn?▸
Q6Speedup tối đa theo Amdahl khi tỉ lệ tối ưu được p = 0.90 và hệ số tăng tốc k tiến vô cùng là bao nhiêu? Ý nghĩa thực tế?▸
k → ∞, công thức rút gọn thành Speedup_max = 1 / (1 - p) = 1 / 0.10 = 10 lần. Dù phần 90% đó chạy tức thời (không tốn thời gian), tổng thể vẫn không thể nhanh hơn 10 lần vì phần 10% còn lại không được tối ưu vẫn chiếm thời gian cố định. Ý nghĩa thực tế: trần này không thể phá vỡ bằng bất kỳ tối ưu nào trong phạm vi đó — muốn speedup hơn 10 lần phải tìm thêm phần khác để tối ưu hoặc thay đổi kiến trúc.Q7Knuth nói "premature optimization is the root of all evil" — câu này có nghĩa là không bao giờ tối ưu, hay nghĩa gì khác?▸
Bài tiếp theo: Out-of-order và SIMD
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên