Cache 总结
這一文,讓我們分析一下,《淺談 Cache》 一文中的奇怪現(xiàn)象,事實(shí)上如今來看也并不奇怪了。
? ? ? ? 在什么情況下 r1 和 r2 都為 0 呢?
? ? ? ? 細(xì)致看代碼,你會發(fā)現(xiàn),兩個線程分別被執(zhí)行在不同的 CPU 核上,而且在線程開始的階段還使用了一個隨機(jī)數(shù),是為了讓兩個線程能盡量同一時候執(zhí)行。
? ? ? ? 如果 CPU0 執(zhí)行:
? ? ? ? x = 1;
? ? ? ? r1 = y;
? ? ? ? CPU1 執(zhí)行:
? ? ? ? y = 1;
? ? ? ? r2 = x;
? ? ? ? 假設(shè)以下的情況發(fā)生:
? ? ? ? x 在 CPU1 的 Cache 中。 y 在 CPU0 的 Cache 中。
? ? ? ? 1. CPU0 運(yùn)行 x = 1, cache miss, 發(fā)送 "read invalidate" 消息,并把 x 的值 1 存入 Store Buffer, 開始運(yùn)行 r1 = y, Cache 命中,r1 為 0;
? ? ? ? 2. CPU1 運(yùn)行 y = 1,cache miss, 發(fā)送 "read invalidate" 消息,并把 y 的值 1 存入 Store Buffer, 開始運(yùn)行 r2 = x,Cache 命中,r2 為 0;
? ? ? ? 3. CPU0 和 CPU1 各自收到了對方的消息,并作出回應(yīng),x 和 y 的值均應(yīng)用到 Cache 中,且都為 1;
? ? ? ? 主函數(shù)收到信號,比較 r1 和 r2 的值,奇跡發(fā)生了。
? ? ? ? 假設(shè)你知道我講的這些細(xì)節(jié),就會發(fā)現(xiàn),事實(shí)上這并非奇怪了。那么假設(shè)解決問題呢?
? ? ? ? 事實(shí)上答案就非常easy了,要么讓兩個線程執(zhí)行在同一個核心上,要么在兩個語句之間加上內(nèi)存屏障,經(jīng)驗(yàn)證,問題攻克了。
? ? ? ? 題外篇:
? ? ? ? 在不同 CPU 架構(gòu)下,對內(nèi)存的亂序訪問事實(shí)上是不同的,一般的內(nèi)存亂序分為下面四種:
? ? ? ? LoadStore, LoadLoad, StoreStore, StoreLoad。而且 X86 下僅僅會出現(xiàn) StoreLoad 亂序,也就是上面的樣例,我的 CPU 是 X86 的,所以出現(xiàn)了這樣的情況,由此可知,事實(shí)上 X86 內(nèi)存亂序訪問的還不算太厲害。
? ? ? ? 簡單解釋一下,x = 1,為 Store, 讀取 y 的過程為 Load,所以 Load 指令在 X86 下同意在 Store 還未更新到 Cache 中之前被運(yùn)行。
? ? ? ? 走出一個誤區(qū),內(nèi)存亂序訪問與 CPU 亂序運(yùn)行(Out of Order,即 OOO)不同。
早期的處理器為有序處理器(In-order processors),有序處理器處理指令通常有下面幾步:
? ? ? ? 1. 指令獲取
?
? ? ? ? 相比之下,亂序處理器(Out-of-order processors)處理指令通常有下面幾步:
? ? ? ? 1. 指令獲取
? ? ? ? 2. 指令被分發(fā)到指令隊(duì)列
? ? ? ? 3. 指令在指令隊(duì)列中等待,直到輸入操作對象可用(一旦輸入操作對象可用,指令就能夠離開隊(duì)列,即便更早的指令未被運(yùn)行)
? ? ? ? 4. 指令被分配到適當(dāng)?shù)墓δ軉卧⑦\(yùn)行
? ? ? ? 5. 運(yùn)行結(jié)果被放入隊(duì)列(而不馬上寫入寄存器堆)
? ? ? ? 僅僅有全部更早請求運(yùn)行的指令的運(yùn)行結(jié)果被寫入寄存器堆后,指令運(yùn)行的結(jié)果才被寫入寄存器堆(運(yùn)行結(jié)果重排序,讓運(yùn)行看起來是有序的)
? ? ? ? 從上面的運(yùn)行過程能夠看出,亂序運(yùn)行相比有序運(yùn)行能夠避免等待不可用的操作對象(有序運(yùn)行的第二步)從而提高了效率。現(xiàn)代的機(jī)器上,處理器運(yùn)行的速度比內(nèi)存快非常多,有序處理器花在等待可用數(shù)據(jù)的時間里已經(jīng)能夠處理大量指令了。
? ? ? ? 如今思考一下亂序處理器處理指令的過程,我們能得到幾個結(jié)論:
? ? ? ? 對于單個 CPU 指令獲取是有序的(通過隊(duì)列實(shí)現(xiàn))
? ? ? ? 對于單個 CPU 指令運(yùn)行結(jié)果也是有序返回寄存器堆的(通過隊(duì)列實(shí)現(xiàn))
? ? ? ? 由此可知,在單 CPU 上,不考慮編譯器優(yōu)化導(dǎo)致亂序的前提下,多線程運(yùn)行不存在內(nèi)存亂序訪問的問題
? ? ? ? CPU 盡管是亂序運(yùn)行,可是是順序流出結(jié)果,在我們看來,亂序運(yùn)行對我們來講是透明的,我們看到的結(jié)果和指令順序是一樣的。
? ? ? ? 在什么情況下 r1 和 r2 都為 0 呢?
? ? ? ? 細(xì)致看代碼,你會發(fā)現(xiàn),兩個線程分別被執(zhí)行在不同的 CPU 核上,而且在線程開始的階段還使用了一個隨機(jī)數(shù),是為了讓兩個線程能盡量同一時候執(zhí)行。
? ? ? ? 如果 CPU0 執(zhí)行:
? ? ? ? x = 1;
? ? ? ? r1 = y;
? ? ? ? CPU1 執(zhí)行:
? ? ? ? y = 1;
? ? ? ? r2 = x;
? ? ? ? 假設(shè)以下的情況發(fā)生:
? ? ? ? x 在 CPU1 的 Cache 中。 y 在 CPU0 的 Cache 中。
? ? ? ? 1. CPU0 運(yùn)行 x = 1, cache miss, 發(fā)送 "read invalidate" 消息,并把 x 的值 1 存入 Store Buffer, 開始運(yùn)行 r1 = y, Cache 命中,r1 為 0;
? ? ? ? 2. CPU1 運(yùn)行 y = 1,cache miss, 發(fā)送 "read invalidate" 消息,并把 y 的值 1 存入 Store Buffer, 開始運(yùn)行 r2 = x,Cache 命中,r2 為 0;
? ? ? ? 3. CPU0 和 CPU1 各自收到了對方的消息,并作出回應(yīng),x 和 y 的值均應(yīng)用到 Cache 中,且都為 1;
? ? ? ? 主函數(shù)收到信號,比較 r1 和 r2 的值,奇跡發(fā)生了。
? ? ? ? 假設(shè)你知道我講的這些細(xì)節(jié),就會發(fā)現(xiàn),事實(shí)上這并非奇怪了。那么假設(shè)解決問題呢?
? ? ? ? 事實(shí)上答案就非常easy了,要么讓兩個線程執(zhí)行在同一個核心上,要么在兩個語句之間加上內(nèi)存屏障,經(jīng)驗(yàn)證,問題攻克了。
? ? ? ? 題外篇:
? ? ? ? 在不同 CPU 架構(gòu)下,對內(nèi)存的亂序訪問事實(shí)上是不同的,一般的內(nèi)存亂序分為下面四種:
? ? ? ? LoadStore, LoadLoad, StoreStore, StoreLoad。而且 X86 下僅僅會出現(xiàn) StoreLoad 亂序,也就是上面的樣例,我的 CPU 是 X86 的,所以出現(xiàn)了這樣的情況,由此可知,事實(shí)上 X86 內(nèi)存亂序訪問的還不算太厲害。
? ? ? ? 簡單解釋一下,x = 1,為 Store, 讀取 y 的過程為 Load,所以 Load 指令在 X86 下同意在 Store 還未更新到 Cache 中之前被運(yùn)行。
? ? ? ? 走出一個誤區(qū),內(nèi)存亂序訪問與 CPU 亂序運(yùn)行(Out of Order,即 OOO)不同。
早期的處理器為有序處理器(In-order processors),有序處理器處理指令通常有下面幾步:
? ? ? ? 1. 指令獲取
? ? ? ? 2. 假設(shè)指令的輸入操作對象(input operands)可用(比如已經(jīng)在寄存器中了),則將此指令分發(fā)到適當(dāng)?shù)墓δ軉卧小<僭O(shè)一個或者多個操作對象不可用(一般是因?yàn)轫氁獜膬?nèi)存中獲取),則處理器會等待直到它們可用;
? ? ? ? 3. 指令被適當(dāng)?shù)墓δ軉卧\(yùn)行
? ? ? ? 4. 功能單元將結(jié)果寫回寄存器堆(Register file,一個 CPU 中的一組寄存器)?
? ? ? ? 相比之下,亂序處理器(Out-of-order processors)處理指令通常有下面幾步:
? ? ? ? 1. 指令獲取
? ? ? ? 2. 指令被分發(fā)到指令隊(duì)列
? ? ? ? 3. 指令在指令隊(duì)列中等待,直到輸入操作對象可用(一旦輸入操作對象可用,指令就能夠離開隊(duì)列,即便更早的指令未被運(yùn)行)
? ? ? ? 4. 指令被分配到適當(dāng)?shù)墓δ軉卧⑦\(yùn)行
? ? ? ? 5. 運(yùn)行結(jié)果被放入隊(duì)列(而不馬上寫入寄存器堆)
? ? ? ? 僅僅有全部更早請求運(yùn)行的指令的運(yùn)行結(jié)果被寫入寄存器堆后,指令運(yùn)行的結(jié)果才被寫入寄存器堆(運(yùn)行結(jié)果重排序,讓運(yùn)行看起來是有序的)
? ? ? ? 從上面的運(yùn)行過程能夠看出,亂序運(yùn)行相比有序運(yùn)行能夠避免等待不可用的操作對象(有序運(yùn)行的第二步)從而提高了效率。現(xiàn)代的機(jī)器上,處理器運(yùn)行的速度比內(nèi)存快非常多,有序處理器花在等待可用數(shù)據(jù)的時間里已經(jīng)能夠處理大量指令了。
? ? ? ? 如今思考一下亂序處理器處理指令的過程,我們能得到幾個結(jié)論:
? ? ? ? 對于單個 CPU 指令獲取是有序的(通過隊(duì)列實(shí)現(xiàn))
? ? ? ? 對于單個 CPU 指令運(yùn)行結(jié)果也是有序返回寄存器堆的(通過隊(duì)列實(shí)現(xiàn))
? ? ? ? 由此可知,在單 CPU 上,不考慮編譯器優(yōu)化導(dǎo)致亂序的前提下,多線程運(yùn)行不存在內(nèi)存亂序訪問的問題
? ? ? ? CPU 盡管是亂序運(yùn)行,可是是順序流出結(jié)果,在我們看來,亂序運(yùn)行對我們來講是透明的,我們看到的結(jié)果和指令順序是一樣的。
轉(zhuǎn)載于:https://www.cnblogs.com/gcczhongduan/p/4065365.html
總結(jié)
- 上一篇: 刚用江南有要注意的地方么?
- 下一篇: java sundry tips