1.盡量避免線程之間共享變量,如果需要共享,變量結構定義為Cache line對齊。
Cache取數據是按照cache line為單位(我們的系統下64Byte),數據跨越兩個cache line,就意味着兩次load或者兩次store。如果數據結構是cache line對齊的,就有可能減少一次讀寫。數據結構的首地址cache line對齊,意味着可能有內存浪費(特別是數組這樣連續分配的數據結構),所以需要在空間和時間兩方面權衡。
例如a[8],然后每個線程訪問固定一個下標來訪問,並且存在寫操作,這樣會使效率暴降,除非把a的數據類型是按CPU的cache line大小定義的。
2. 頻繁更新和不頻繁更新的數據分開存放,讀數據和寫數據分開。實際操作具有一定的難度。
3.數據的分布盡可能的按照訪問順序定義。實際操作具有一定的難度。
4.減少使用全局變量,增強數據訪問的局部性。
程序的運行存在時間和空間上的局部性,前者是指只要內存中的值被換入緩存,今后一段時間內會被多次引用,后者是指該內存附近的值也被換入緩存。函數操作數據最好都在棧上,因為局部性的數據很可能被載入到cache了,頻繁訪問局部性數據也會使得對應的cache數據不被頻繁替換出cache(在64位機器上,局部變量會優先放到寄存器中),而全局變量存儲在全局數據段,在一個被反復調用的函數體內,引用該變量需要對緩存多次換入換出。
循環體內的代碼要盡量精簡,因為代碼(就是指令?)是放在L1指令緩存(I-Cache)里的,而L1指令緩存只有幾K字節大小,如果對某段代碼需要多次讀取,而這段代碼又跨越一個L1緩存大小,那么緩存優勢將盪然無存。
5.增強數據訪問的局部性也是減少cache miss 的最直接方法,編譯器所作的優化很多也只是在不改變程序邏輯的情況下增加數據訪問局部性。但編譯器這方面能做的很有限。
6.數據訪問遵循cache預取的時間局部性和空間局部性。
Cache取數據是按照cache line為單位,當讀取內存中某個地址數據時,該地址附近的數據也被一起換入cache,所以程序訪問數據時也要遵循此規則。cache的命中率對多層循環的影響是最明顯的,因此在設計循環邏輯的時候,如果有某個數據結構需要多次訪問,盡量讓其全部在最里層中完成訪問。還有遍歷二維數組時要先遍歷二維下標等。
7.在寫多核程序時,盡量綁定線程到一個cpu上。因為不同cpu之間L1, L2 cache是獨立的。
8.減少函數指針使用。
編譯器完全看不出函數指針指向的規律,分支預測器對這種分支地址預測也是兩眼一抹黑,所以要盡量避免用函數指針。如果一個應用程序超過幾M大小而且還會調用一些動態庫,這樣會造成函數指針在很廣闊的空間飛來飛去,會導致cache命中率大幅降低,但是沒有太好的辦法。