同樣一段 C++ code,即使 algorithmic complexity 完全相同,產生的 machine code 也可能因為 branch pattern、instruction dependency、memory access pattern 或資料分布不同,而呈現完全不同的 latency characteristics。這也是 Low-Latency C++ 的下一個階段 —— 從「如何寫出更有效率的 C++」進一步深入到「CPU 如何執行這些 C++」。
1. Branch Prediction
首先需要理解的是 Branch Prediction。現代 CPU 不會單純等待 if-else statement 或 loop condition 的結果出來之後才決定下一條 instruction。CPU 會嘗試預測 branch 的方向,提前 fetch、decode,甚至開始執行預測路徑上的 instructions。如果 prediction 成功,pipeline 可以持續前進;但如果 prediction 失敗,CPU 就必須丟棄 speculative execution 的結果並重新填充 pipeline。這就是 branch misprediction,而它對 latency 的影響往往遠高於單純多執行幾條 instruction。
2. Instruction Pipeline
CPU 會將 instruction execution 拆成不同階段,使多條 instruction 能夠同時處於不同 stage。Pipeline 的存在讓 CPU 能夠維持非常高的 instruction throughput,但也帶來 dependency、stall、flush 等問題。即使是 source code 中看似連續且簡單的 operation,到了 CPU 層級未必會以相同的順序、相同的時間成本被執行。
3. Out-of-Order Execution
現代 superscalar CPU 並不一定按照 machine code 的順序逐條執行 instruction,而是會在不違反 dependency 的前提下,尋找可以平行執行的 instructions。這使 CPU 能夠隱藏部分 latency,例如在等待一個較慢的 load 時,同時執行其他彼此獨立的 computation。
因此,理解 latency 時不能只看這條 instruction 本身需要多少 cycles,而需要思考它是否位於 critical dependency chain 上。某個 operation 即使本身 latency 很高,只要可以與其他工作重疊,實際 impact 可能並不大;反過來,一個看似普通的 dependency 如果位於 critical path 上,反而可能成為整段 execution 的 bottleneck。
4. CPU Cache Hierarchy
從 registers、L1、L2、L3 到 main memory,不同層級具有完全不同的 latency 與 bandwidth characteristics。當 data structure 與 memory access pattern 沒有被妥善設計時,CPU 可能花費大量時間等待資料,而不是執行真正的 computation。
這也是為什麼在 low-latency programming 中,cache locality、working set size、cache line、spatial locality、temporal locality、hardware prefetcher 以及 false sharing 都非常重要。尤其在多執行緒系統中,一個看似只是「修改一個 counter」的 operation,如果造成 cache line 在不同 core 之間反覆轉移,就可能產生遠超預期的 latency。
5. Memory Ordering
當系統進入 multi-core、multi-threaded environment 後,「程式碼寫在前面」與「CPU 一定先執行它」並不是完全相同的概念。Compiler、CPU execution engine 以及 cache coherence protocol 都可能影響 memory operations 的實際可見順序。因此,C++ memory model、atomic、memory ordering、cache coherence、fence 以及 synchronization cost,都會成為 low-latency system 必須理解的核心概念。
更重要的是,這些因素並不是彼此獨立的。一次 branch misprediction 可能造成 pipeline flush;一次 cache miss 可能讓 out-of-order execution 暫時找不到足夠的 independent work;一個 atomic operation 可能受到 memory ordering 與 cache coherence 的共同影響;而一個不理想的 data layout 又可能同時增加 cache miss、TLB pressure 與 memory-level parallelism 的負擔。
最終的目標並不是背誦「某個 instruction 是幾個 cycles」或「某個 CPU cache 是幾 ns」,因為不同 CPU generation、clock frequency、memory hierarchy 與 runtime condition 都可能改變這些數字。真正重要的是建立一套 mental model。Source code 只是起點,machine code 才是 CPU 真正看到的東西,而 microarchitecture 才決定這些 instructions 最終如何被執行。
而這也代表最佳化的方式開始改變:不再只是猜測哪段 code 比較慢,而是提出可驗證的 microarchitectural hypothesis。這會把 Low-Latency C++ 從單純的 coding technique,帶進真正的 performance engineering。