彙編效率優化 打破依賴鏈

2022-04-03 09:42:07 字數 1508 閱讀 3278

如下面的乙個c++例子,目的是計算長度為100的陣列的總和:

// example 9.3a, loop-carried dependency chain

double list[100], sum = 0.;

for (int i = 0; i < 100; i++) sum += list[i];

上述**中有100次加法運算,並且每次加法運算都依賴於上一次加法運算的結果。這是乙個迴圈依賴鏈,一旦迴圈依賴鏈非常長,就有可能長時間使得out-of-order處理機制處於無法有效運轉的狀態。在上述例子中,在執行浮點運算時,只有加法運算i ++ 可以並行執行。

現假設浮點加法的latency為4,throughput為1,那麼最優的實現方式就是並行執行4個浮點加法,如此一來就能充分利用浮點加法器的流水線機制。我們可以把**修改如下:

// example 9.3b, multiple accumulators

double list[100], sum1 = 0., sum2 = 0., sum3 = 0., sum4 = 0.;

for (int i = 0; i < 100; i += 4)

sum1 = (sum1 + sum2) + (sum3 + sum4);

這下迴圈裡面有了四條依賴鏈,並且每條依賴鏈的長度為原來的四分之一,由於四條依賴鏈相互獨立,因此可以並行執行,優化後的**理論上的處理速度為原來的四倍。不過依賴鏈越多,cpu進行指令排程的難度也就越大,因此不一定能達到理論值。

現在的微處理器執行速度越來越快,可能在乙個時鐘週期可以同時處理4到5條指令,支援macro-op fusion的處理器甚至能處理更多,對於並行度越高的處理器,就更應該避免過長的依賴鏈。

普通線性運算可以打破依賴鏈:

y = a + b + c + d;
上述式子可以寫成以下形式:

y = (a + b) + (c + d);
如此一來a+b與c+d就能並行執行。不過可能有些編譯器已經對這種形式的運算做了優化。

暫存器清零可以用 mov eax, 0 、 xor eax, eax 、 sub eax, eax、 sbb eax, eax 等多種方式。不過常用的是 xor 與 sub,原因如下面**:

instruction

length

side-effect

mov eax, 0

5 bytes

-xor eax, eax

2 bytes

-sub eax, eax

2 bytes

-sbb eax, eax

2 bytes

depend on carry flag

我們之前也說過,對partial register寫入是會導致falce dependence的,因此對partial register的清零是不能打破依賴鏈的。另外,現代處理器對清零操作的處理不用經過處理階段,可以直接在暫存器重新命名階段對物理暫存器清零並對接上邏輯暫存器。

simd的相關暫存器也是有相應的xor,sub等指令,也常用於打破依賴鏈。

索引效率優化

索引是提高資料查詢最有效的方法,也是最難全面掌握的技術,因為正確的索引可能使效率提高10000倍,而無效的索引可能是浪費了資料庫空間,甚至大大降低查詢效能。索引的管理成本 1 儲存索引的磁碟空間 2 執行資料修改操作 insert update delete 產生的索引維護 3 在資料處理時回需額外...

索引效率優化

索引是提高資料查詢最有效的方法,也是最難全面掌握的技術,因為正確的索引可能使效率提高10000倍,而無效的索引可能是浪費了資料庫空間,甚至大大降低查詢效能。索引的管理成本 1 儲存索引的磁碟空間 2 執行資料修改操作 insert update delete 產生的索引維護 3 在資料處理時回需額外...

kafka 效率優化

資料分割槽儲存 在topic內設定了多個分割槽,使kafka的消費者程序池能得到有序性保證和負載均衡,使得topic對應的消費組裡的消費者們各自可以獨享乙個分割槽。如此的話,每個消費者是其消費的分割槽的唯一reader,在單個reader下當然保證了有序這件事。而且多個分割槽也使得負載可以比較平衡。...