總體認識,
cpu的cache通常較大, 比如 128KB, 被劃分為多個有固定大小的cache line, cache line通常是32Byte或64Byte.
CPU內部的cache種類, 至少有三種
1) 指令cache
2) 資料cache 通常有多級 multi-level
3) TLB 加速虛擬位址2物理地址轉換
cache entry (cache條目)
包含如下部分
1) cache line : 從主存一次copy的資料大小)
2) tag : 標記cache line對應的主存的地址
3) falg : 標記當前cache line是否invalid, 如果是資料cache, 還有是否dirty
cpu訪問主存的規律
1) cpu從來都不直接存取主存, 都是通過cache間接訪問主存
2) 每次需要訪問主存時, 遍曆一遍全部cache line, 尋找主存的地址是否在某個cache line中.
3) 如果cache中沒有找到, 則分配一個新的cache entry, 把主存的記憶體copy到cache line中, 再從cache line中讀取.
cache中包含的cache entry條目有限, 所以, 必須有合適的cache淘汰策略
一般使用的是LRU策略.
將一些主存地區標記為non-cacheble, 可以提高cache命中率, 降低沒用的cache
回寫策略
cache中的資料更新後,需要回寫到主存, 回寫的時機有多種
1) 每次更新都回寫. write-through cache
2) 更新後不回寫,標記為dirty, 僅當cache entry被evict時才回寫
3) 更新後, 把cache entry送如回寫隊列, 待隊列收集到多個entry時批量回寫.
cache一致性問題
有兩種情況可能導致cache中的資料到期
1) DMA, 有其他裝置直接更新主存的資料
2) SMP, 同一個cache line存在多個CPU各自的cache中. 其中一個CPU對其進行了更新.
cpu stall cpu失速
指的是當cache miss時(特別是read cache miss), cpu在等待資料從記憶體讀進去cache中期間, 沒事可做.
解決此問題的方法有
1) 超執行緒技術. CPU在硬體層面, 把一個CPU類比成兩個CPU, 在上層看來是兩個CPU. 並發的執行兩個線程. 這樣當一個線程因cache miss在等待時, 另一個線程可以執行.
主存的一個地址, 需要被映射進哪個cache line? (術語:Associativity)
根據映射策略的不同而不同
1) 最笨的, 一個地址可被映射進任意cache line (fully associative)
帶來的問題是, 當尋找一個地址是否已經被cache時, 需要遍曆每一個cache line來尋找, 這個代價不可接受.
就像停車位可以大家隨便停一樣, 停的時候簡單的, 找車的時候需要一個一個停車位的找了.
你想下, cpu想知道一個地址是否已經在cache中了, 需要把全部cache line找一邊, 那該有多慢?
2) Direct Mapped Cache (相當於1-way associative)
這個就是相當於hash了, 每個地址能被映射到的cache line是固定的.
每個人的停車位是固定分配好的. 可以直接找到.
缺點是, 因為人多車少, 很可能幾個人爭用同一個車位, 導致cache 淘汰頻繁. 需要頻繁的從主存讀取資料到cache, 這個代價也較高.
由於cache中cache line的個數都是2的指數個. 那麼, hash演算法就很簡單了, 不用模數, 直接把記憶體位址的某幾個bit位拿出來即可. 比如cache line有128(2^7)個, cache line的大小是32(2^5)位元組,
那麼一個32位地址的 0~4位作為cache line內部位移, 5~11位作為cache line的索引即可. 剩下的bit12~31作為當前cache line的tag. tag的作用時, 當有另外一個地址也映射到同一個cache line時, tag用來比較兩個地址是不是同一個地址. 畢竟同一個cache-line可以對應的記憶體的位置非常多個的.
3) 2-way associative
是fully associative和Direct Mapped Cache的折中.
2-way, 每一個人可以有兩個停車位, 這樣當一個停車位被佔了的時候, 還有機會尋找另外一個. 雖然人數眾多, 但同時來找停車位的人並不多. (相當於很多人的車在外面,沒有開回來)
所以, 2-way associative近似的相當於有了2倍大小的cache, 使用Direct Mapped Cache策略.
注意, 這圖只統計了cache miss率, 很顯然full-associative是做好的. 但是full-associative導致的判斷一個地址是否在cache中的代價是非常昂貴的.所以, 生產環境一般都是2-way associative ======================================================
多線程變成中避免以及識別錯誤的共用變數方式 主要解決在SMP環境下cache line被頻繁重新整理的的問題 Avoiding and Identifying False Sharing Among Threads
http://software.intel.com/en-us/articles/avoiding-and-identifying-false-sharing-among-threads/ 舉例:
// 如下代碼在SMP環境下存在cache頻繁重新整理問題double sum=0.0, sum_local[NUM_THREADS];#pragma omp parallel num_threads(NUM_THREADS){ int me = omp_get_thread_num(); sum_local[me] = 0.0; #pragma omp for for (i = 0; i < N; i++) sum_local[me] += x[i] * y[i]; #pragma omp atomic sum += sum_local[me];}
因為sum_local數組是個全域變數, 多個線程都會訪問, 並且, 各個線程訪問的地方很接近, 會導致一個線程更新, 其他CPU的cache line失效. 解決該問題的方法是
1) 不同線程之間盡量少的訪問全域變數, 盡量使用線程局部變數.
2) 如果一定要訪問, 盡量讓各個線程自己訪問的地區cacheline對齊.
3) 頻繁更新的儲存和不頻繁更新的儲存分開.