(筆記)CPU & Memory, Part 2: CPU caches

博文:https://chanjarster.github.io...git

原文:What every programmer should know about memory, Part 2: CPU caches程序員

關鍵詞:Cache prefetching、TLB cache missing、MESI protocol、Cache types(L1d、L1i、L二、L3)github

3.1 CPU Caches in the Big Picture

內存很慢,這就是爲什麼CPU cache存在的緣由,CPU cache內置在CPU內部,SRAM。
CPU cache尺寸不大。數組

CPU cache處於CPU和內存之間,默認狀況下CPU所讀寫的數據都存在cache中。緩存

Intel將CPU cache分爲data cache和code cache,這樣會有性能提高。安全

隨着CPU cache和內存的速度差別增大,在二者之間增長了更大可是更慢的CPU cache,爲什麼不擴大原CPU cache的尺寸?答案是不經濟。多線程

現代CPU core擁有三級緩存。架構

L1d是data cache,L1i是instruction cache(code cache)。上圖只是概要,現實中從CPU core到內存的數據流一路上能夠經過、也能夠不經過各個高層cache,這取決於CPU的設計者,這部分對於程序員是不可見的。併發

每一個處理器擁有多個core,每一個core幾乎擁有全部硬件資源的copy,每一個core能夠獨立運行,除非它們用到了相同的資源。
每一個core有用多個thread,每一個thread共享其所屬處理器的全部資源,Intel的thread僅對reigster作分離,甚至這個也是有限制的,有些register依然是共享的。dom

上面這張圖:

  1. 兩個處理器,processors,大的灰色矩形
  2. 每一個處理器有一個L3 cache和L2 cache(從上往下看第一個深綠色L3 cache,第二個較淺綠色L2 cache)
  3. 每一個處理器有兩個core(小的灰色矩形)
  4. 每一個core有一個L1d cache和L1i cache(兩個淺綠色矩形)
  5. 每一個core有兩個thread,紅色矩形,同一個processor的全部core都共享相同的L2/L3 cache

3.2 Cache Operation at High Level

插播概念word

Word,數據的天然單位,CPU指令集所能處理的數據單位。在x86-64架構中,word size=64 bits=8 bytes。

CPU cache中存儲的條目(entry)不是word,而是cache line,現在一條cache line大小爲64 bytes。每次從RAM中抓取數據的時候不只會將目標數據抓過來,還會將其附近的數據一併抓過來,構成64 bytes大小的cache line。

當一個cache line被修改了,可是尚未被寫到內存(main memory),則這個cache line被標記爲dirty。一旦被寫到內存,則dirty標記被清除。

對於多處理器系統,處理器之間會互相監視寫動做,並維持如下規則:

  • A dirty cache line is not present in any other processor's cache.
  • Clean copies of the same cache line can reside in arbitrarily many caches.

Cache eviction類型:

  • exclusive,當要加載新數據的時候,若是L1d已滿,則須要將cache line推到L2,L2轉而推到L3,最終推到main memory。優勢:加載新數據的時候只須要碰L1d。缺點:eviction發生時代價逐級增高。
  • inclusive,L1d中的全部cache line一樣存在於L2中。優勢:L1d eviction快,由於只須要碰L2。缺點:浪費了一些L2的空間。

下表是Intel奔騰M處理訪問不一樣組件所需的CPU週期:

To Where Cycles
Register <= 1
L1d ~3
L2 ~14
Main Memory ~240

下圖是寫不一樣尺寸數據下的性能表現:

根據經驗能夠推測出L1d size=2^12=4K,L2 size=2^20=1M。當數據<=4K時,正好可以放進L1d中,操做的CPU週期<10個。當數據>4K and <=1M時,會利用到L2,操做的CPU週期<75。當數據>1M時,CPU操做週期>400,則說明沒有L3,此時是直接訪問內存了。

很是重要:下面的例子裏CPU訪問數據是按照如下邏輯:

  • CPU只能從L1d cache訪問數據
  • 若是L1d沒有數據,則得先從L2把數據加載到L1d
  • 若是L2沒有數據,則得先從main memory(RAM)加載數據

也就是說若是這個數據一開始在L1d、L2都不存在,那麼就得先從main memory加載到L2,而後從L2加載到L1d,最後CPU才能夠訪問。

3.3 CPU Cache Implementation Details

3.3.1 Associativity

沒看懂。略。

3.3.2 Measurements of Cache Effects

keyword:cache prefetching、TLB cache miss

測試方法是順序讀一個l的數組:

struct l {
  struct l *n;
  long int pad[NPAD];
};

根據NPAD不一樣,元素的大小也不一樣:

  • NPAD=0,element size=8 bytes,element間隔 0 bytes
  • NAPD=7,element size=64 bytes,element間隔 56 bytes
  • NPAD=15,element size=128 bytes,element間隔 120 bytes
  • NPAD=31,element size=256 bytes,element間隔 248 bytes

被測CPU L1d cache=16K、L2 cache=1M、cache line=64 bytes。

Single Threaded Sequential Access

case 1: element size=8 bytes

下面是NPAD=0(element size=8 bytes,element間隔0 bytes),read 單個 element的平均時鐘週期:

Figure 3.10: Sequential Read Access, NPAD=0

上面的Working set size是指數組的總尺寸(bytes)。

能夠看到就算數據尺寸超過了16K(L1d size),對每一個元素的讀的CPU週期也沒有到達14,甚至當數據尺寸超過1M(L2 size)時也是這樣,這就是由於cache prefetching的功勞:

  • 當你在讀L1d cache line的時候,處理器預先從L2 cache抓取到L1d cache,當你讀L1d next cache line的時候這條cache line已經準備好了。
  • 而L2也會作prefetching動做

Cache prefetching是一項重要的優化手段,在使用連續的內存區域的時候,處理器會將後續數據預先加載到cache line中,也就是說當在訪問當前cache line的時候,下一個cache line的數據已經在半路上了。

Prefetching發生既會發生在L1中也會發生在L2中。

case 2: element size >= cache line, 總尺寸 <= L2

各個尺寸element的狀況:

  • NPAD=7(element size=64 bytes,element間隔56 bytes)
  • NPAD=15,element size=128 bytes,element間隔 120 bytes
  • NPAD=31,element size=256 bytes,element間隔 248 bytes

Figure 3.11: Sequential Read for Several Sizes

觀察working set size <= L2,看(210~219區段):

  • 當working set size <= L1d的時候,時鐘週期和NPAD=0持平。
  • 當working set size > L1d <= L2的時候,時鐘週期和L2自己的週期吻合,在28左右

這是爲何呢?此時prefetching沒有起到做用了嗎?這是由於:

  • prefetching自己須要時鐘週期
  • 順序read array實際上就是在順序read cache line
  • 當NPAD=0時,element size=8,就意味着你要read屢次纔會用光cache line,那麼prefetching能夠穿插在read之間進行,將next cache line準備好
  • 當NPAD=7,15,31時,element size>=cache line,那麼就意味着每次read都把一條cache line用完,沒有留給prefetching的時鐘週期了,下一次read的時候就只能老實從L2加載,因此時鐘週期在28左右。
case 3: selement size >= cache line, 總尺寸 > L2

仍是看各個尺寸element的狀況:

  • NPAD=7(element size=64 bytes,element間隔56 bytes)
  • NPAD=15,element size=128 bytes,element間隔 120 bytes
  • NPAD=31,element size=256 bytes,element間隔 248 bytes

Figure 3.11: Sequential Read for Several Sizes

觀察working set size > L2,看(219以後的區段):

  • NPAD=7(element size=64),依然有prefetching的跡象
  • NPAD=15(element size=128)、NPAD=31(element size=256)則沒有prefetching的跡象

這是由於處理器從size of the strides判斷NPAD=15和31,小於prefetching window(具體後面會講),所以沒有啓用prefetching。而元素大小妨礙prefetching的硬件上的緣由是:prefetching沒法跨過page boundaries。

而NPAD=15與31的差異很大則是由於TLB cache miss

測量TLB效果

TLB是用來存放virtual memory address到physical memory address的計算結果的(虛擬內存地址和物理內存地址在後面會講)。

測試NPAD=7(element size=64),每一個元素按照下列兩種方式排列的性能表現:

  • On cache line,數組中的每一個元素連續。也就是每次迭代須要新的cache line,每64個元素須要一個新page。
  • On page,數組中的每一個元素在獨立的Page中。每次迭代須要新的cache line。

Figure 3.12: TLB Influence for Sequential Read

  • 藍色曲線看到,當數據量超過212 bytes(4K),曲線開始飆升。所以能夠推測TLB的大小爲4K。
  • 由於每一個元素大小爲64 bytes,所以能夠推測TLB的entry數目爲64.
  • 從虛擬內存地址計算物理內存地址,並將結果放到TLB cache中是很耗時的。
  • main memory讀取或從L2讀取數據到cache line以前,必須先計算物理內存地址。
  • 能夠看到越大的NPAD就越會下降TLB cache的效率。換句話說,越大的元素尺寸就越會下降TLB cache的效率。
  • 因此address translation(地址翻譯)的懲罰會疊加到內存訪問上,因此Figure 3.11的NPAD=31(element size=256)週期數會比其餘更高,並且也比理論上訪問RAM的週期高。
case 4: Sequential Read and Write, NPAD=1

測試NPAD=1,element size=16 bytes,順序讀與寫:

  • Follow,和以前同樣是順序讀的測試結果,做爲baseline
  • Inc,每次迭代對pad[0]++
  • Addnext0,每次迭代讀取下一個元素的pad[0],把值加到本身的pad[0]上

Figure 3.13: Sequential Read and Write, NPAD=1

按照常理來講,Addnext0應該比較慢由於它作的工組比較多,然而在某些working set size下反而比Inc要好,這是由於:

  • Addnext0的讀取下一個元素的pad[0]這個動做其實是force prefetch。當程序讀取下一個元素的pad[0]的時候,數據已經存在於cache line之中了。
  • 因此只要working set size符合可以放到L2中,Addnext0的表現和Follow同樣好

下面這段沒看懂,也許這不重要。

The 「Addnext0」 test runs out of L2 faster than the 「Inc」 test, though. It needs more data loaded from main memory. This is why the 「Addnext0」 test reaches the 28 cycles level for a working set size of 2 21 bytes. The 28 cycles level is twice as high as the 14 cycles level the 「Follow」 test reaches. This is easy to explain, too. Since the other two tests modify memory an L2 cache eviction to make room for new cache lines cannot simply discard the data. Instead it has to be written to memory. This means the available bandwidth on the FSB is cut in half, hence doubling the time it takes to transfer the data from main memory to L2.
case 4: Sequential Read on larger L2/L3

測試NPAD=15,element size=128 bytes。

Figure 3.14: Advantage of Larger L2/L3 Caches

  • 最後一級cache越大,則曲線逗留於L2訪問開銷對應的低等級的時間越長
  • 第二個處理器在220時比第一個處理快一倍,是由於它的L3
  • 第三個處理器表現的更好則是由於它的4M L2

因此緩存越大越能獲得性能上的提高。

Single Threaded Random Access Measurements

以前已經看處處理器經過prefetching cache line到L2和L1d的方法,能夠隱藏main memory的訪問開銷,甚至L2的訪問開銷。可是,只有在內存訪問可預測的狀況下,這才能工做良好。

下圖是順序訪問和隨機訪問的對比:

Figure 3.15: Sequential vs Random Read, NPAD=0

後面的沒有看懂。

3.3.3 Write behavior

cache應該是coherent的,cache的coherency對於userlevel 代碼應該是徹底透明的,內核代碼除外。

若是一個cache line被修改了,那麼自此時間點以後的系統的結果和壓根沒有cache而且main memory被修改的結果是同樣。有兩個實現策略:

Write-through:

  • 一旦cache line被寫,則立刻將cache line寫到main memory
  • 老是保證main memory和cache保持一致,不管何時cache line被替換,所cache的內容能夠隨時丟棄
  • 優勢:實現起來最簡單
  • 缺點:雖然簡單可是不快。若是一個程序不停的修改一個本地變量,會佔用FSB帶寬。

Write-back:

  • cache line被寫不立刻寫到main memory,僅標記爲dirty。
  • 當cache line在以後的某個時間點被drop,dirty標記會指導處理器把內容寫到main memory
  • 絕大多數系統採用的是這個策略
  • 處理器甚至能夠在驅散cache line以前,利用FSB的空閒空間存儲cache line的內容。這樣一來就容許清除dirty標記,當須要新空間的時候,處理器就可以直接丟棄cache line。

還有另外兩個策略,它們都用於地址空間的特殊區域,這些區域不禁實際的RAM支持。

Write-combining:

  • Write-combining是一種首先的cache優化策略,更多的用於設備的RAM上,好比顯卡。
  • 傳輸數據到設備的開銷比訪問本地RAM要大得多,因此要儘量避免傳輸次數太多。
  • 若是僅由於cache line的一個word的修改而要把整個cache line都傳輸太浪費。
  • 所以,write-combining把多個寫訪問合併在一塊兒,而後再把cache line寫出去。
  • 這能夠加速訪問設備RAM的速度。

我的插播:

這個策略犧牲了必定的latency,可是提升了throughput,相似於批處理。

Uncacheable:

  • 內存地址壓根就不存在RAM裏,這些地址通常都是硬編碼的。
  • 在商業機器上,通常來講這些地址會被翻譯成訪問card和鏈接到某個總線的設備(PCIe)。
  • 這些內存不該該被緩存。

3.3.4 Multi-processor support

在多處理器系統和多核處理器中,對於全部不共享的cache,都會存在cache內容不一致的問題。兩個處理器之間不會共享L1d、L1i、L二、L3,同一處理器的兩個核之間至少不會共享L1d。

提供一條能從A處理器直接訪問B處理器的cache的通道是不切實際的,由於速度不夠快。因此比較實際的作法是將cache內容傳輸到其餘處理器以備不時之需。對於多核處理器也採用這種作法。

那何時傳輸呢?當一個處理器須要一條cache line作讀/寫,可是它在其餘處理器裏是dirty時。

那麼一個處理器是如何決定一條cache line在另一個處理器裏是否dirty呢?一般來講內存訪問是讀,而讀不會把一個cache line變成dirty。處理器每次對cache line的寫訪問以後都把cache line信息廣播出去是不切實際的。

MESI cache coherency protocol

開發了MESI緩存協同協議(MESI cache coherency protocol),規定了一條cache line的狀態有四種:Modified、Exclusive、Shared、Invalid。

  • Modified: 本地處理器剛修改cache line,也意味着它是全部cache中的惟一copy。
  • Exclusive: cache line沒有被修改,但已知沒有被加載到任何其餘處理的cache中。
  • Shared: cache line沒有被修改,可能存在於其餘處理器的cache中。
  • Invalid: cache line無效,好比還未被使用。
MESI所解決的問題和分佈式緩存中數據同步的問題是一致的,好好看看,這可以帶來一些啓發

使用這四個狀態有可能有效率地實現write-back策略,同時支持多處理器併發使用read-only數據。

Figure 3.18: MESI Protocol Transitions

下面是四種狀態變化的解讀:

Invalid

  • 最開始全部的cache line都是空的,也即Invalid
  • 若是數據加載到cache line的目的是爲了寫,變成Modified
  • 若是數據加載到cache line的目的是爲了讀,

    • 若其餘處理器是否也加載了相同的cache line,變成Shared
    • 若是沒有,變成Exclusive

Modified

  • 若是一條Modified cache line被本地處理器讀or寫,狀態不變。
  • 若是B處理器要讀A處理器的Modified cache line,則A處理器必須將內容發送給B處理器,而後狀態變成Shared。

    • 發送給B處理器的數據一樣也被memory controller接收和處理,而後存到main memory裏。
    • 若是這一步沒有作,那麼狀態就不能變成Shared。
  • 若是B處理器要寫A處理器的Modified cache line,則A處理器要把數據傳給B,而後標記爲Invalid。

    • 這就是臭名昭著的Request For Ownership(RFO)(經過address bus)。在最後一級緩存執行這個操做和I->M同樣,代價相對來講是比較高的
    • 對於write-through cache,則必須加上在高一級cache寫新的cache line,或者寫到main memory的時間,進一步增長了代價

Shared

  • 若是本地處理器讀一條Shared cache line,狀態不變。
  • 若是本地寫一條Shared cache line,則變成Modified。

    • 全部其餘處理器的cache line copy變成Invalid。
    • 因此寫操做必須經過RFO廣播到其餘處理器。
  • 若是B處理器要讀A處理器的Shared cache line,狀態不變。
  • 若是B處理器要寫A處理器的Shared cache line,則變成Invalid,不牽涉到bus operation。

Exclusive

  • Exlusive和Shared同樣除了一個區別:本地寫不須要RFO。
  • 因此處理器會盡量把多的cache line維持在Exclusive狀態,而不是Shared狀態。

    • 當信息不足的時候,Shared狀態做爲一種fallback——原文沒有說明白是什麼信息,猜想應該是當沒法知道其餘處理器是否擁有相同cache line的時候,就把它設爲Shared,這樣作會比較安全
    • E->M 比 S->M 快得多
因此在多處理器系統中,除了填充cache line以外,咱們還得關注RFO消息對性能的影響。只要出現了RFO消息,就會變慢。

有兩種場景RFO消息是必須的:

  1. 一個線程從一個處理器遷移到另外一個處理器,全部的cache line都必須一次性移動到新處理器
  2. 同一條cache line是真的被兩個處理器須要。

    • 在稍小一點的尺度上,多核處理器內部就存在這樣的狀況,只是代價小一點而已,RFO可能會被髮送不少次。

影響Coherency protocol速度的因素:

  • RFO出現的頻率,應該越低越好
  • 一次MESI狀態變化必須等全部處理器都應答以後才能成功,因此最長的可能應答時間決定了coherency protocol的速度。
  • FSB是共享資源,大多數系統全部處理器經過同一條bus連到memory controller。若是一個處理器飽和了FSB,則共享同一bus的兩個或四個處理器將進一步限制每一個處理器可用的帶寬。
  • 就算每一個處理器有本身的bus鏈接memory controller(Figure 2.2),那麼處理器到memory module的bus仍是會被共享的。
  • 在多線程/多進程程序裏,總有一些synchronization的需求,這些synchronization則是使用memory實現的,因此就會有一些RFO消息。
  • 因此concurrency嚴重地受限於可供synchronization的有限帶寬。
  • 程序應該最小化從不一樣處理器、不一樣核訪問同一塊內存區域的操做。

Multi Threaded Measurements

用以前相同的程序測試多線程的表現,採用用例中最快的線程的數據。全部的處理器共享同一個bus到memory controller,而且只有一條到memory modules的bus。

case 1: Sequential Read Access, Multiple Threads

Figure 3.19: Sequential Read Access, Multiple Threads

Figure 3.19: Sequential Read Access, Multiple Threads

這個測試裏沒有修改數據,全部cache line都是shared,沒有發生RFO。可是即使如此2線程的時候有18%的性能損失,在4線程的時候則是34%。那麼可能的緣由就只多是一個或兩個瓶頸所形成的:處理器到memory controller的bus、memory controller到memory modules的bus。一旦working set超過L3尺寸以後,就要從main memory prefetch數據了,帶寬就不夠用了

case 2: Sequential Increment, Multiple Threads

這個測試用的是 「Sequential Read and Write, NPAD=1,Inc」,會修改內存。

Figure 3.20: Sequential Increment, Multiple Threads

Figure 3.20: Sequential Increment, Multiple Threads

注意圖中的Y軸不是線性增長的,因此看上去很小的差別實際上差異很大。

2線程依然有18%的性能損失,而4線程則有93%的性能損失,這意味4線程的時候prefetch流量核write-back流量飽和了bus。

圖中也能夠發現只要有多個線程,L1d基本上就很低效了。

L2倒不像L1d,彷佛沒有什麼影響。這個測試修改了內存,咱們預期會有不少RFO消息,可是並無看見二、4線程相比單線程有什麼性能損失。這是由於測試程序的關係。

case 3: Random Addnextlast, Multiple Threads

下面這張圖主要是爲了展示使人吃驚的高數字,在極端狀況下處理list中的單個元素竟然要花費1500個週期。

Figure 3.21: Random Addnextlast, Multiple Threads

總結case 一、二、3

把case 一、二、3中的最大working set size的值總結出多線程效率:

#Threads Seq Read Seq Inc Rand Add
2 1.69 1.69 1.54
4 2.98 2.07 1.65

Table 3.3: Efficiency for Multiple Threads

這個表顯示了在最大working set size,使用多線程能得到的可能的最好的加速。理論上2線程應該加速2,4線程應該加速4。好好觀察這個表裏的數字和理論值的差別。

下面這張圖顯示了Rand Add測試,在不一樣working set size下,多線程的加速效果:

Figure 3.22: Speed-Up Through Parallelism

L1d尺寸的測試結果,在L2和L3範圍內,加速效果基本上是線性的,一旦當尺寸超過L3時,數字開始下墜,而且2線程和4線程的數字下墜到同一點上。這也就是爲何很難看到大於4個處理器的系統使用同一個memory controller,這些系統必須採用不一樣的構造。

不一樣狀況下上圖的數字是不同的,這個取決於程序究竟是怎麼寫的。在某些狀況下,就算working set size能套進最後一級cache,也沒法得到線性加速。可是另外一方面依然有可能在大於兩個線程、更大working set size的狀況下得到線性加速。這個須要程序員作一些考量,後面會講。

special case: hyper-threads

Hyper-Threads(有時候也被稱爲Symmetric Multi-Threading, SMT),是CPU實現的一項技術,同時也是一個特殊狀況,由於各個線程並不可以真正的同時運行。超線程共享了CPU的全部資源除了register。各個core和CPU依然是並行運行的,可是hyper-threads不是。CPU負責hyper-threads的分時複用(time-multiplexing),噹噹前運行的hyper-thread發生延遲的時候,就調度令一個hyper-thread運行,而發生延遲的緣由大部分都是因內存訪問致使的。

當程序的運行2線程在一個hyper-thread核的時候,只有在如下狀況纔會比單線程更有效率:2個線程的運行時間之和低於單線程版本的運行時間。這是由於當一個線程在等待內存的時候能夠安排另外一個線程工做,而本來這個是串形的。

一個程序的運行時間大體能夠用下面這個簡單模型+單級cache來計算:

Texe = N[(1-Fmem)Tproc + Fmem(GhitTcache + (1-Ghit)Tmiss)]

  • N = Number of instructions. 指令數
  • Fmem = Fraction of N that access memory. N的幾分之幾訪問內存
  • Ghit = Fraction of loads that hit the cache. 加載次數的幾分之幾命中cache
  • Tproc = Number of cycles per instruction. 每條指令的週期數
  • Tcache = Number of cycles for cache hit. 命中cache的週期數
  • Tmiss = Number of cycles for cache miss. 沒命中cache的週期數
  • Texe = Execution time for program. 程序的執行時間

爲了使用兩個線程有​​意義,兩個線程中每一個線程的執行時間必須至可能是單線程代碼的一半。若是把單線程和雙線程放到等式的兩遍,那麼惟一的變量就是cache命中率。不使線程執行速度下降50%或更多(下降超過50%就比單線程慢了),而後計算所需的最小cache命中率,獲得下面這張圖:

Figure 3.23: Minimum Cache Hit Rate For Speed-Up

X軸表明了單線程代碼的Ghit,Y表明了雙線程代碼所需的Ghit,雙線程的值永遠不能比單線程高,不然的話就意味着單線程能夠用一樣的方法改進代碼了。單線程Ghit < 55%的時候,程序老是可以從多線程獲得好處。

綠色表明的是目標區域,若是一個線程的降速低於50%且每一個線程的工做量減半,那麼運行時間是有可能低於單線程的運行時間的。看上圖,單線程Ghit=60%時,若是要獲得好處,雙線程程序必須在10%以上。若是單線程Ghit=95%,多線程則必須在80%以上,這就難了。特別地,這個問題是關於hyper-threads自己的,實際上給每一個hyper-thread的cache尺寸是減半的(L1d、L二、L3都是)。兩個hyper-thread使用相同的cache來加載數據。若是兩個線程的工做集不重疊,那麼原95%也可能減半,那麼就遠低於要求的80%。

因此Hyper-threads只在有限範圍的場景下有用。單線程下的cache命中率必須足夠低,並且就算減半cache大小新的cache命中率在等式中依然可以達到目標。也只有這樣使用Hyper-thread纔有意義。在實踐中是否可以更快取決於處理器是否可以充分交疊一個線程的等待和另外一個線程的執行。而代碼爲了並行處理所引入的其餘開銷也是要考慮進去的。

因此很明白的就是,若是兩個hyper-threads運行的是兩個徹底不一樣的代碼,那麼確定不會帶來什麼好處的,除非cache足夠大到可以抵消因cache減半致使的cache miss率的提升。除非操做系統的工做負載由一堆在設計上真正可以從hyper-thread獲益的進程組成,可能仍是在BIOS裏關掉hyper-thread比較好。

3.3.5 Other Details

現代處理器提供給進程的虛擬地址空間(virtual address space),也就是說有兩種地址:虛擬的和物理的。

虛擬地址不是惟一的:

  • 一個虛擬地址在不一樣時間能夠指向不一樣的物理地址。
  • 不一樣進程的相同的地址也可能指向不一樣的物理地址。

處理器使用虛擬地址,虛擬地址必須在Memory Management Unit(MMU)的幫助下才能翻譯成物理地址。不過這個步驟是很耗時的(注:前面提到的TLB cache緩存的是虛擬->物理地址的翻譯結果)。

現代處理器被設計成爲L1d、L1i使用虛擬地址,更高層的cache則使用物理地址。

一般來講沒必要關心cache地址處理的細節,由於這些是不能改變的。

Overflowing the cache capacity是一件壞事情;若是大多數使用的cache line都屬於同一個set,則全部緩存都會提早遇到問題。第二個問題能夠經過virtual address來解決,可是沒法避免user-level進程使用物理地址來緩存。惟一須要記住的事情是,要盡一切可能,不要在同一個進程裏把相同的物理地址位置映射到兩個或更多虛擬地址

Cache replacement策略,目前使用的是LRU策略。關於cache replacement程序員沒有什麼事情可作。程序員能作的事情是:

  • 徹底使用邏輯內存頁(logical memory pages)
  • 使用盡量大的頁面大小來儘量多樣化物理地址

3.4 Instruction Cache

指令cache比數據cache問題更少,緣由是:

  • 被執行的代碼量取決於所需代碼的大小,而代碼大小一般取決於問題複雜度,而問題複雜度是固定的。
  • 程序的指令是由編譯器生成的,編譯器知道怎麼生成好代碼。
  • 程序流程比數據訪問內存更容易預測,現代處理器很是擅長預測模式,這有助於prefetching
  • 代碼老是具備良好的空間、時間局部性。

CPU核心和cache(甚至第一級cache)的速度差別在增長。CPU已被流水線化,所謂流水線指一條指令的執行是分階段的。首先指令被解碼,參數被準備,最後被執行。有時候這個pipeline會很長,長pipeline就意味着若是pipeline中止(好比一條指令的執行被中斷了),那它得花一些時間才能從新找回速度。pipeline中止老是會發生的,好比沒法正確預測下一條指令,或者花太長時間加載下一條指令(好比從內存里加載)。

現代CPU設計師花費了大量時間和芯片資產在分支預測上,爲了儘量不頻繁的發生pipeline中止。

3.4.1 Self Modifying Code

早些時候爲了下降內存使用(內存那個時候很貴),人們使用一種叫作Self Modifing Code(SMC)的技術來減小程序數據的尺寸。

不過如今應該避免SMC,由於若是處理器加載一條指令到流水線中,而這條指令在卻又被修改了,那麼整個工做就要從頭來過。

因此如今處處理器假設code pages是不可變的,因此L1i沒有使用MESI,而是使用更簡單的SI

3.5 Cache Miss Factors

咱們已經看到內存訪問cache miss致使的開銷極具增大,可是有時候這個問題是沒法避免的,因此理解實際的開銷以及如何緩解這個問題是很重要的。

3.5.1 Cache and Memory Bandwidth

測試程序使用x86和x86-64處理器的SSE指令每次加載16 bytes,working set size從1K到512M,測試的是每一個週期可以加載/存儲多少個bytes。

Figure 3.24: Pentium 4 Bandwidth

這個圖是64-bit Intel Netburst處理器的測試結果。

先看讀的測試能夠看到:

  • 當working set size在L1d之內的時候,16 bytes/cycle
  • 當L1d不夠用的時候,性能降低很快,6 bytes/cycle
  • 到218的時候又降低了一小段是由於DTLB耗盡了,意思是對每一個新page須要額外工做。
  • 這個測試是順序讀的,很利於prefetching,而在任何working set size下都可以達到5.3/cycle,記住這些數字,它們是上限,由於真實程序永遠沒法達到這個值。

在看寫和copy的測試:

  • 就算是最小的working set size,也不超過4 bytes/cycle。這說明Netburst處理器使用的Write-through策略,L1d的速度顯然受制於L2的速度。
  • copy測試是把一塊內存區域copy到另外一個不重疊的內存區域,由於上述緣由它的結果也沒有顯著變差。
  • 當L2不夠用的時候,降低到0.5 bytes/cycle。

下面是兩個線程分別釘在同一核心的兩個Hyper-thread上的測試狀況:

Figure 3.25: P4 Bandwidth with 2 Hyper-Threads

這個結果符合預期,由於Hyper-thread除了不共享register以外,其餘資源都是共享的,cache和帶寬都被減半了。這意味着就算一個線程在在等待內存的時候可讓另外一個線程執行,可是另外一個線程實際上也在等待,因此並無什麼區別。

下圖是Intel Core2處理器的測試結果(對比Figure 3.24):

Figure 3.26: Core 2 Bandwidth

Core 2 L2是P4 L2的四倍。這延遲了write和copy測試的性能下跌。read測試在全部的working set size裏都維持在16 bytes/cycle左右,在220處下跌了一點點是由於DTLB放不下working set。可以有這麼好的性能表現不只意味着處理器可以及時地prefetching和傳輸數據,也意味着數據是被prefetch到L1d的。

看write和copy的表現,Core 2處理器的L1d cache沒有采用Write-through策略,只有當必要的時候L1d纔會evict,因此可以得到和read接近的表現。當L1d不夠用的時候,性能開始降低。當L2不夠用的時候再下跌不少。

下圖是Intel Core2處理器雙線程跑在兩個核心上:

Figure 3.27: Core 2 Bandwidth with 2 Threads

這個測試兩個線程分別跑在兩個核心上,兩個線程訪問相同的內存,沒有完美地同步。read性能和Figure 3.26沒有什麼差異。

看write和copy的表現,有意思的是在working set可以放進L1d的表現和直接從main memory讀取的表現同樣。兩個線程訪問相同的內存區域,針對cache line的RFO消息確定會被髮出。這裏能夠看到一個問題,RFO的處理速度沒有L2快。當L1d不夠用的時候,會將修改的cache line flush到共享的L2,這是性能有顯著提高是由於L1d中的數據被flush到L2,那就沒有RFO了。並且由於兩個核心共享FSB,每一個核心只擁有一半的FSB帶寬,意味着每一個線程的性能大約是單線程的一半。

廠商的不一樣版本CPU和不一樣廠商的CPU的表現都是不一樣的。原文裏後面比較了AMD Opteron處理器,這裏就不寫了。

3.2.5 Critical Word Load

數據是一block爲單位從main memory傳輸到cache line的,一個block大小爲64 bits(記得前面說的word也是64 bits),一條cache line的大小是64/128 bytes,因此填滿一個cache line要傳輸8/16次。

後面沒有看懂,大體意思是Critical word是cache line中的關鍵word,程序要讀到它以後才能繼續運行,可是若是critical word不是cache line的第一個,那麼就得等前面的word都加載完了以後才行。blah blah blah,不過這個理解可能也是不對的。

3.5.3 Cache Placement

cache和hyper-thread、core、處理器的關係是程序員沒法控制的,可是程序員能夠決定線程在哪裏執行,因此cache和CPU是如何關聯的就顯得重要了。

後面沒仔細看,大體講了因爲不一樣的CPU架構,決定如何調度線程是比較複雜的。

3.5.4 FSB Influence

不細講了,對比了667MHz DDR2和800 MHz DDR2(20%的提高),測試下來性能有18%的提高接近理論值(20%)。

Figure 3.32: Influence of FSB Speed

因此更快的FSB的確可以帶來好處。要注意CPU可能支持更高的FSB,可是主板/北橋可能不支持的狀況。

相關文章
相關標籤/搜索