程式效能最佳化練習題 (Practice - Program Optimization)


Question 1 - Memory Aliasing [recall]

考慮 twiddle1(long *xp, long *yp) { *xp += *yp; *xp += *yp; }twiddle2(long *xp, long *yp) { *xp += 2* *yp; }
為什麼編譯器不能把 twiddle1 最佳化成 twiddle2 的形式?當 xp == yp 時兩者結果各是原值的幾倍?


Question 2 - Function Side Effects [recall]

設全域變數 long counter = 0;long f() { return counter++; }
func1() 回傳 f()+f()+f()+f(),func2() 回傳 4*f()。兩者回傳值各為何?這說明編譯器的什麼限制?


Question 3 - CPE 與週期時間 [recall]

某程式處理 n 個元素的執行時間量測為 T=368+9.0n 個時脈週期,執行於 4 GHz 處理器。
(a) 此程式的 CPE 為何?(b) 該處理器的週期時間為多少?


Question 4 - 版本交叉比較 [application]

兩個版本的執行時間分別為 T1=60+35nT2=136+4n(單位:cycles,n 為整數元素數)。
求 n 在哪個範圍時版本 1 較快?


Question 5 - 兩大效能下界 [recall]

何謂 latency bound 與 throughput bound?參考機器整數加法有 4 個功能單元、issue time = 1,為何 combining 運算的 throughput bound 是 0.50 而非 0.25?


Question 6 - 指令解碼為 Micro-operations [recall]

addq %rax,%rdxaddq %rax,8(%rdx) 各被解碼成幾個 primitive operations?一個 store 需要幾個功能單元?


Question 7 - Speculative Execution 與程式狀態 [recall]

處理器對分支做 speculative execution,若之後發現預測錯誤,為什麼程式狀態(暫存器、記憶體)不會被破壞?


Question 8 - k × 1 展開的邊界 [recall]

以展開因子 k 做 k × 1 loop unrolling(單一累加器)處理長度 n 的向量:主迴圈的上限條件怎麼寫?收尾迴圈會執行幾次?此轉換能突破 latency bound 嗎?


Question 9 - 多重累加器 CPE 預測 [application]

在參考機器上(double 乘法 latency = 5、2 個 FP 乘法單元、issue time = 1),對 double * 的 combining 迴圈做 5 × 5 展開(5 個平行累加器)。
(a) 預測 CPE 約為多少?(b) 要達到 throughput bound 0.50,展開因子至少要多大?


Question 10 - Reassociation 轉換 [application]

2 × 1 展開的迴圈本體為 acc = (acc OP data[i]) OP data[i+1];。只移動一對括號改成:

acc = acc OP (data[i] OP data[i+1]);

對 double *(L = 5)而言,CPE 大約從多少變成多少?為什麼?


Question 11 - 整數 vs 浮點重排的正確性 [recall]

多重累加器與 reassociation 都改變了運算順序。為什麼整數版轉換在所有情況下結果都與原版相同,而浮點版可能不同?


Question 12 - Horner 法為何更慢 [analysis]

多項式求值兩版本:poly 每迭代執行 result += a[i]*xpwr; xpwr = x*xpwr;(每迭代 2 乘 1 加);polyh(Horner 法)執行 result = a[i] + x*result;(每迭代 1 乘 1 加)。
參考機器 FP 乘法 L=5、FP 加法 L=3。Horner 法運算次數較少,為何實測 CPE(8.00)反而比 poly(5.00)差?


Question 13 - 不可預測分支與 cmov [recall]

某函式對隨機資料 CPE ≈ 13.5、對排序過(規律)的資料 CPE ≈ 3.5。診斷原因,並說明如何改寫程式碼消除此差距。


Question 14 - Register Spilling [recall]

把 combine 系列從 10 × 10 展開加大到 20 × 20 展開,整數加法 CPE 反而從 0.55 升到 0.83。解釋原因。


Question 15 - Load Latency 與 Linked List [recall]

計算 linked list 長度的迴圈核心為 ls = ls->next;,實測 CPE = 4.00。這個 4 從哪裡來?為什麼一般陣列走訪的 load 不受此限制?


Question 16 - Write/Read Dependency [analysis]

函式 write_read(long *src, long *dst, long n) 內迴圈為 *dst = val; val = (*src) + 1; cnt--;
Example A 以 src != dst 呼叫,CPE = 1.3;Example B 以 src == dst 呼叫,CPE = 7.3。
分析兩者 critical path 的差異,並說明 store buffer 在其中的角色。


Question 17 - Dest Aliasing 與 combine4 [application]

combine3 每迭代把累積值寫回 *dest;combine4 改用區域變數 acc 累積、迴圈結束才寫一次 *dest
設整數乘法、IDENT = 1v = [2, 3, 5],以 dest = get_vec_start(v) + 2(alias 到最後一個元素)呼叫,兩版本的最終向量各為何?這說明什麼?