CPUCache是位於CPU與內存之間的臨時存儲器,容量比內存小但交換速度卻比內存要快得多。Cache的出現主要是爲了解決CPU運算速度與內存讀寫速度不匹配的矛盾,由於CPU運算速度要比內存讀寫速度快不少,會使CPU花費很長時間等待數據到來或把數據寫入內存。在Cache中的數據是內存中的一小部分,可是CPU短期內即將訪問的,當CPU調用大量數據時,就可避開內存直接從Cache中調用,從而加快讀取速度。Cache對CPU的性能影響很大,主要是由於CPU的數據交換順序和CPU與Cache間的帶寬引發的。
Cache工做原理是當CPU要讀取一個數據時,首先從Cache中查找,若是找到就當即讀取並送給CPU處理;若是沒有找到,就用相對慢的速度從內存中讀取並送給CPU處理,同時把數據所在的數據塊調入Cache中,可使得之後對整塊數據的讀取都從Cache中進行,沒必要再調用內存。
CPU讀取Cache的命中率很是高(大多數CPU可達90%左右),大大節省了CPU直接讀取內存的時間,也使CPU讀取數據時基本無需等待。
按照數據讀取順序和與CPU結合的緊密程度,CPU Cache能夠分爲L1 Cache、L2 Cache、L3 Cache,每一級Cache中所儲存的所有數據都是下一級Cache的一部分,三種Cache的技術難度和制形成本是相對遞減的,因此其容量也是相對遞增的。當CPU要讀取一個數據時,首先從L1 Cache中查找,若是沒有找到再從L2 Cache中查找,若是仍是沒有就從L3 Cache或內存中查找。一般每級Cache的命中率大概都在80%左右,即所有數據量的80%均可以在L1 Cache中找到,只剩下20%的總數據量才須要從L2 Cache、L3 Cache或內存中讀取,所以L1 Cache是CPU Cache架構中最爲重要的部分。node
Intel CPU中,808六、80286時代CPU是沒有Cache的,由於當時CPU和內存速度差別並不大,CPU直接訪問內存。但80386開始,CPU速度要遠大於內存訪問速度,第一次出現了Cache,並且最先的Cache並無放在CPU模塊,而放在主板上。CPU Cache的材質SRAM比內存DRAM貴不少,大小以MB爲單位,所以現代計算機在CPU和內存之間引入了高速Cache,做爲CPU和內存之間的通道。通過長期發展演化,逐步演化出了L一、L二、L3三級緩存結構,並且都集成到CPU芯片。
L1 Cache最接近於CPU,速度最快,但容量最小。現代CPU的L1 Cache會分紅兩個:數據Cache和指令Cache,指令和數據的更新策略並不相同,並且由於CISC的變長指令,指令Cache要作特殊優化。每一個CPU核都有本身獨立的L1 Cache和L2 Cache,但L3 Cache一般是整顆CPU共享。python
Linux內核開發者定義了CPUFreq系統查看CPU詳細信息,/sys/devices/system/cpu目錄保存了CPU詳細信息。
L1 Cache查看
L2 Cache查看
L3 Cache查看
CPU Cache查看命令以下:算法
dmidecode -t cache getconf -a | grep CACHE
CPU只有3級Cache,L4爲0,L1 Cache數據緩存和指令緩存分別爲32KB,L2 Cache爲256KB,L3 Cache爲3MB,Cache Line爲64字節。ASSOC表示主存地址映射到緩存的策略,L一、L2是8路組相聯,L3是12路組相聯。
L1 Cache中數據Cache和指令Cache是物理CPU核共享的,所以,超線程技術虛擬出來的邏輯CPU核CPU0和CPU1共享L1 Cache。 緩存
Cache Line是CPU Cache中的最小緩存單位,是本級Cache向下一層取數據時的基本單位。目前主流的CPU Cache的Cache Line大小都是64Bytes,即當程序須要從內存中讀取一個字節的時候,相鄰的63字節同時會從內存中加載到CPU Cache中,當CPU訪問相鄰的數據的時候,並不會從內存中讀取數據,而從CPU Cache中便可訪問到數據,提升了速度。
L一、L二、L3的Cache Line大小都是64字節,能夠經過以下查看:cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
CPU寄存器、Cache、內存的性能指標參考
CPU要訪問的數據在Cache中有緩存,稱爲hit(命中),反之則稱爲miss(缺失)。
當CPU試圖從某地址load數據時,首先從L1 Cache中查詢是否命中,若是命中則把數據返回給CPU。若是L1 Cache缺失,則繼續從L2 Cache中查找。當L2 Cache命中時,數據會返回給L1 Cache以及CPU。若是L2 Cache、L3 Cache也缺失,須要從主存中load數據,將數據返回給L3 Cache、L2 Cache、L1 Cache及CPU。性能優化
每一個CPU核都有一個私有的L1 Cache,對於多核CPU,不一樣CPU核之間的L1 Cache須要保證一致性。
(1)Bus Snooping Protocol
當某個CPU核修改本身私有Cache時,硬件會廣播通知到總線上其它全部CPU核。每一個CPU核都有特殊的硬件監聽廣播事件,並檢查是否有相同的數據被緩存在本身的Cache。
Bus Snooping須要時刻監聽總線上的一切活動,在必定程度上加劇了總線負載,增長了讀寫延遲。
(2)MESI協議
MESI協議是一種使用狀態機機制下降帶寬壓力來維護多核Cache一致性的協議。
MESI協議名稱來源於cache line的4種狀態Modified、Exclusive、Shared、Invalid的簡寫。當cache line狀態是Modified或者Exclusive狀態時,修改其數據不須要發送消息給其它CPU,在必定程度上減輕了帶寬壓力。
多核Cache一致性由硬件保證,現代CPU硬件採用的一致性協議一般是MESI協議的變種,如ARM64架構採用MOESI協議。服務器
MMU(Memory Management Unit)是一種經過段機制和頁機制將虛擬地址轉換爲物理地址的硬件電路,一般集成在CPU芯片內。
MMU經過創建頁表,完成虛擬地址到物理地址的轉換。當須要訪問內存中的一個數據,經過數據的虛擬地址查找頁表,一旦在頁表命中,就經過找到的物理地址尋址到內存中的數據。若是頁表中沒有命中,表示頁表中沒有創建數據虛擬地址到物理地址的映射,經過缺頁異常,創建數據的頁表映射項。
對於頻繁使用的不變數據,每次都回進行查找數據和相應頁表項,爲了加快頁表查找速度,減小沒必要要的重複,引入TLB。架構
TLB( Translation Look- aside Buffer)專門用於緩存內存中的頁表項,一般集成在MMU內部。TLB是一個很小的Cache,TLB表項( TLB entry)數量比較少,每一個TLB表項包含一個頁面的相關信息,例若有效位、虛擬頁號、修改位、物理頁幀號等。當處理器要訪問一個虛擬地址時,首先會在TLB中查詢。若是TLB表項中沒有命中,就須要訪問頁表來計算出相應的物理地址。若是TLB表項中命中,直接從TLB表項中獲取物理地址。
TLB內部存放的基本單位是TLB表項,TLB容量越大,所能存放的TLB表項就越多,TLB命中率就越高。可是TLB容量是有限的,目前Linux內核默認採用4KB大小的小頁面,若是一個程序使用512個小頁面,即2MB大小,那麼至少須要512個TLB表項才能保證不會出現TLB Miss。但若是使用2MB大小的大頁,只須要一個TLB表項就能夠保證不會出現TLB Miss。對於消耗內存以GB爲單位的大型應用程序,還可使用以1GB爲單位的大頁,從而減小TLB Miss的狀況。
TLB本質是頁表的Cache,TLB緩存了最近使用的數據的頁表項(虛擬地址到物理地址的映射)。TLB的出現是爲了加快訪問內存數據的速度,減小重複的頁表查找,不是必須的,但TLB能夠提升訪問內存數據的速度。ide
當CPU要訪問一個虛擬地址/線性地址時,CPU會首先根據虛擬地址的高20位(X86架構)在TLB中查找。若是是表中沒有命中,須要經過訪問慢速RAM中的頁表計算出相應的物理地址。同時,物理地址被存放在一個TLB表項中,之後對同一線性地址的訪問,直接從TLB表項中獲取物理地址便可。
TLB結構以下:
若是一個須要訪問內存中的一個數據,給定數據的虛擬地址,查詢TLB,發現有(hit),直接獲得物理地址,在內存根據物理地址讀取數據。若是TLB沒有命中(miss),只能經過頁表來查找。oop
TLB內部存放的基本單位是TLB表項,對應着RAM中存放的頁表條目。頁表條目的大小固定不變的,因此TLB容量越大,所能存放的頁表條目越多,TLB命中越大。但TLB容量畢竟是有限的,所以RAM頁表和TLB表項沒法作到一一對應。
(1)全相連
全相連方式,TLB cache中的表項和線性地址之間沒有任何關係,一個TLB表項能夠和任意線性地址的頁表項關聯。全相連方式關聯方式使得TLB表項空間的利用率最大,但延遲也可能很大,由於每次CPU請求,TLB硬件都把線性地址和TLB表項逐一比較,直到TLB命中或者全部TLB表項比較完成。隨着CPU Cache愈來愈大,須要比較大量TLB表項,因此只適合小容量TLB。
(2)直接匹配
每個線性地址塊均可經過模運算對應到惟一的TLB表項,只需進行一次比較,下降了TLB內比較延遲,但產生衝突的很大,致使TLB miss的發生,下降了命中率。
(3)組相連
爲了解決全相連內部比較效率低和直接匹配的衝突,引入了組相連。組相連把全部的TLB表項分紅多個組,每一個線性地址塊對應的再也不是一個TLB表項,而是一個TLB表項組。CPU作地址轉換時,首先計算線性地址塊對應哪一個TLB表項組,而後在TLB表項組按順序比對。按照組長度,能夠分爲2路、4路、8路。
通過長期工程實踐,發現8路組相連是一個性能分界點。8路組相連的命中率和全相連命中率至關,超過8路,組內對比延遲帶來的缺點會超過命中率提升的優點。性能
超線程(Hyperthreading)技術在一個物理核上模擬兩個邏輯核,兩個邏輯核具備各自獨立的寄存器(eax、ebx、ecx、msr等)和APIC,但會共享使用物理核的執行資源,包括執行引擎、L1/L2 Cache、TLB和系統總線等等。
超線程技術僅僅是在一個物理核心上使用了兩個物理任務描述符,物理計算能力並無增長。採用多worker設計應用,在超線程的幫助下,兩個被調度到同一核心不一樣超線程的worker,經過共享Cache、TLB,大幅下降了任務切換開銷。在某個worker不忙的時候,超線程容許其它的worker也能使用物理計算資源,有助於提高物理核總體的吞吐量。但因爲超線程對物理核執行資源的爭搶,業務的執行時延也會相應增長。
開啓超線程後,物理核總計算能力是否提高以及提高的幅度和業務模型相關,平均提高在20%-30%左右
當超線程相互競爭時,超線程的計算能力相比不開超線程時的物理核會降低30%左右
超線程開啓或關閉取決於應用模型,對於時延敏感型任務,在節點負載太高時會引起超線程競爭,任務執行時長會顯著增長,應該關閉超線程;對於後臺計算型任務,建議開啓超線程提升整個節點的吞吐量。
NUMA架構出現前,CPU朝着高頻率方向發展收到物理極限的挑戰,轉而向着多核心的方向發展。因爲全部CPU Core都經過共享一個北橋來讀取內存,隨着CPU核數增長,北橋在響應時間上的性能瓶頸愈來愈明顯。所以,硬件設計師把內存控制器從北橋中拆分出來,平分到每一個CPU Core上,即NUMA架構。
NUMA(Non-Uniform Memory Access)起源於AMD Opteron微架構,同時被Intel Nehalem採用。
NUMA架構將整臺服務器分爲若干個節點,每一個節點上有單獨的CPU和內存。只有當CPU訪問自身節點內存對應的物理地址時,纔會有較短的響應時間(後稱Local Access)。而若是須要訪問其餘節點的內存的數據時,就須要經過inter-connect通道訪問,響應時間就相比以前變慢了(後稱Remote Access)。因此NUMA(Non-Uniform Memory Access)就此得名
在 Node 內部,架構相似SMP,使用 IMC Bus 進行不一樣核心間的通訊;不一樣的 Node 間經過QPI(Quick Path Interconnect)進行通訊。
一個Socket(內存插槽)對應一個Node,QPI的延遲要高於IMC Bus,CPU訪問內存存在遠近(remote/local)。
NUMA架構的內存分配策略對於進程或線程是不公平的。RHEL Linux中,localalloc是默認的NUMA內存分配策略,即從當前node上請求分配內存,會致使資源獨佔程序很容易將某個node的內存用盡。而當某個node的內存耗盡時,Linux恰好將CPU node分配給某個須要消耗大量內存的進程(或線程),此時將產生swap。
(1)BIOS關閉NUMA
BIOS的Memory Setting菜單找到Node Interleaving項,設置爲Disabled表示啓用NUMA,非一致訪問方式訪問,是默認配置;設置爲Enabled表示關閉NUMA,採用SMP方式啓用內存交錯模式。
(2)軟件層次關閉NUMA
啓動應用前使用numactl –interleave=all修改NUMA策略便可。
若是應用會佔用大規模內存,應該選擇關閉NUMA Node限制(或從硬件關閉NUMA);若是應用並不佔用大內存,而是要求更快的程序運行時間,應該選擇限制只訪問本NUMA Node方法來進行處理。
檢查BIOS是否開啓NUMAgrep -i numa /var/log/dmesg
檢查當前系統的NUMA節點numactl --hardware
lscpu
查看內存數據
numastat
當發現numa_miss數值比較高時,說明須要對分配策略進行調整,如將進程綁定到指定CPU上,從而提升內存命中率。
在開啓NUMA支持的Linux中,Kernel不會將任務內存從一個NUMA node遷移到另外一個NUMA node。
進程一旦啓動,所在NUMA node就不會被遷移,爲了儘量的優化性能,在正常的調度之中,CPU的core也會盡量的使用能夠local訪問的本地core,在進程的整個生命週期之中,NUMA node保持不變。
一旦當某個NUMA node負載超出另外一個node一個閾值(默認25%),則認爲須要在此node上減小負載,不一樣的NUMA結構和不一樣的負載情況,系統見給予一個延時任務的遷移——相似於漏杯算法。在這種狀況下將會產生內存的remote訪問。
NUMA node之間有不一樣的拓撲結構,不一樣node間的訪問會有一個距離概念,可使用numactl -H進行查看。
numactl [--interleave nodes] [--preferred node] [--membind nodes] [--cpunodebind nodes] [--physcpubind cpus] [--localalloc] command
--interleave=nodes, -i nodes用於設定內存的交織分配模式,系統在爲多個節點分配內存空間時,將會以輪詢分發的方式被分配給多個節點。若是在當前衆多的交織分配內存節點中的目標節點沒法正確的分配內存空間,內存空間將會由其它的節點來分配。
--membind=nodes, -m nodes從指定節點中分配內存空間,若是節點內存空間不足,分配操做將會失敗。
--cpunodebind=nodes, -N nodes用於綁定進程到CPU Node。
--physcpubind, -C cpus用於把進程綁定到CPU核心上。
--localalloc , -l 啓動進程,並在當前CPU節點分配內存。
--preferred=node用於指定優先分配內存空間的節點,若是沒法在節點分配空間,會從其它節點分配。numactl --cpubind=0 --membind=0 python param
numactl --show
顯示當前NUMA機制numactl --hardware
顯示當前系統中有多少個可用的節點。numactl [--huge] [--offset offset] [--shmmode shmmode] [--length length] [--strict]
建立共享內存段