Red Hat Training

A Red Hat training course is available for Red Hat Enterprise Linux

章 4. CPU

CPU 這個名詞代表「中央處理器」(central processing unit),對於大部份系統來說,此名詞容易造成誤解,因為「中央」一般意味著「單一」,而目前大部份的系統皆裝載了超過一顆中央處理器或核心。實體上來講,CPU 會封裝為單一單位,並連接在主機板的「插槽」(socket)中。主機板上的各個插槽皆擁有各別的連接用處:它們可連接其它 CPU 插槽、記憶體控制器、插斷控制器,以及其它周邊裝置。對於作業系統來說,插槽代表 CPU 與相關資源的邏輯分群。大部份時候,我們將會以此概念為中心來討論 CPU 微調。
RHEL 會保留大量有關於系統 CPU 事件的數據;這些數據對於微調並改善 CPU 效能來說相當有幫助。〈節 4.1.2, “微調 CPU 效能”〉中討論了一些較有幫助的數據、如何找到並分析這些數據以進行效能微調。

拓樸

在較舊的系統上,所裝載的 CPU 也較少,這種情況下可使用「SMP」(對稱式多處理器,Symmetric Multi-Processor)架構。這代表系統中的各個 CPU 皆會以相似(或對稱)的方式存取可用的記憶體。近年來,插槽與 CPU 的對應數量已大幅增長,要讓處理器能對稱式存取系統中所有的記憶體,將會耗費極高的成本。目前大部份擁有高 CPU 數量的系統,皆採用了一種名為「NUMA」(非對稱式記憶體存取,Non-Uniform Memory Access)的架構來取代 SMP。
AMD 處理器已透過了其 Hyper Transport(HT)互聯技術採用了此類型的架構一段時間了,而 Intel 則在進行其 Quick Path Interconnect(QPI)設計時,開始實作了 NUMA。因為您為應用程式分配資源時,需要考量系統的「拓樸」,因此 NUMA 與 SMP 需個別進行不同的微調。

執行續

在 Linux 作業系統中,執行動作的單位為「執行續」(thread)。執行續擁有註冊文本、堆疊,以及它們在 CPU 上執行的一項可執行程式碼區段。作業系統(OS)將負責在可用的 CPU 上排程這些執行續。
作業系統會透過在可用的核心上,為執行續進行負載平衡,以優化 CPU 效能。因為作業系統主要僅運用 CPU 資源,擴充性應用程式效能,它可能並未作出最佳的效能。將某個應用程式執行續移至另一插槽上的 CPU 中,效能並不一定會優於等待目前的 CPU,因為進行插槽之間的記憶體存取作業可能會花上更多時間。對於高效能的應用程式來說,應用程式設計者最好事先決定執行續應放置在哪。〈節 4.2, “CPU 排程”〉討論了如何優化分配 CPU 與記憶體,並以最佳的方式執行應用程式執行續。

插斷

插斷」(interrupt,在 Linux 中亦稱為 IRQ)是一項比較不明顯(然而依然重要),卻可影響應用程式效能的系統事件。這些事件由作業系統處理,並由周邊設備使用來發出「資料到達」,或是「作業完成」的訊號,例如網路寫入或是計時器事件。
正在執行應用程式碼的 OS 或 CPU 處理插斷的方式,不會影響應用程式的運作。然而,它可能會影響應用程式的效能。本章節亦同時討論如何避免插斷對應用程式效能所帶來的一些負面影響。

4.1. CPU 拓樸

4.1.1. CPU 與 NUMA 拓樸

最早的電腦處理器為「單一處理器」(uniprocessor),表示電腦只有一個 CPU。當時能平行處理許多程序的假象,是作業系統讓單一處理器快速切換,執行多個執行續。設計師發現將執行指令的時脈調快,只能將系統效能加快到一定程度(以現有科技來說,建立穩定的時脈波形是限制)。要讓系統的整體效能加快,設計師決定在系統上增加 CPU,允許雙處理器平行處理。增加處理器的趨勢至今方興未艾。
早期的大部分多處理器系統中,每個 CPU 連至每個記憶體位置(通常是平行匯流排)的邏輯路徑都是一樣的。這讓系統中的每個 CPU 都以同樣時間存取任何記憶體位置。這類架構稱為 SMP(Symmetric Multi-Processor,對稱多處理架構)系統。在 CPU 數量不多的情形下,SMP 得以運作無礙,可一旦 CPU 增加到一定數量(8 或 16)時,要平行存取記憶體會使用太多系統資源,導致周邊配備缺乏空間。
要在系統中使用更多 CPU,結合了兩種概念:
  1. 序列匯流排
  2. NUMA 拓樸
序列匯流排是單線的通訊路徑,時脈非常高,將需要傳輸的資料封包化高速傳出。硬體設計師開始採用序列匯流排作為 CPU 之間、以及 CPU 與記憶體控制晶片及其它周邊之間的高速連線。這表示與其得在「每個」CPU 到記憶體子系統之間,舖設 32 或 64 條路徑,不如只需「一條」路徑,徹底降低主機板上的所需空間。
同時,硬體設計師透過降低晶片大小,在同樣空間中塞入更多電晶體。設計師不再將一個一個 CPU 直接放在主機板上,而是整合到同一個晶片上,成為單一的多核心處理器。然後,與其讓每個處理器封裝以同樣方式存取記憶體,設計師開始使用 NUMA(非一致性記憶體存取,Non-Uniform Memory Access)策略,讓每個封裝/插槽的組合擁有一或多個專用的記憶體區域,以供高速存取。每個插槽之間也有較慢速的連線,以存取其它插槽的記憶體。
舉一個簡單的 NUMA 範例,假設我們的主機板上有兩個 CPU 插槽,每個插槽上都裝有四核心的處理器。這表示這台系統上總共有八顆 CPU;每個插槽四顆。每個插槽也連上各自 4 GB 的記憶體插槽,亦即系統總共有 8 GB 的記憶體。在此範例中,編號 0-3 的 CPU 位於編號 0 的插槽,4-7 的 CPU 位於插槽 1。此範例中的插槽也相當於 NUMA 的節點。
CPU 0 要存取記憶體 0 可能需要三個時脈週期:第一個週期向記憶體控制晶片出示位址,第二個週期設定對記憶體位置的存取,第三個週期讀取或寫入該位置。然而,因為 CPU 4 位於不同的插槽上,需要透過兩組記憶體控制晶片(先是插槽 1 的本地記憶體控制晶片,然後是插槽 0 的遠端記憶體控制晶片)來存取,因此需要四個週期才能存取同樣區塊的記憶體。如果系統競逐同樣的記憶體位置,(亦即超過一個 CPU 同時存取同樣位置的記憶體),記憶體控制晶片需要加以仲裁、將存取過程序列化,這樣一來存取記憶體的時間就會加長。增加快取的一致性(確保本地 CPU 的快取包含同樣記憶體位置的同樣資料)會讓這過程更加複雜。
Intel (Xeon) 與 AMD (Opteron) 的最新高階處理器都有 NUMA 拓樸。AMD 處理器之間的連線稱為 HT(HyperTransport),而 Intel 的稱為 QPI(QuickPath Interconnect)。兩者的分別在於連接 CPU、記憶體或周邊裝置的實體連線,但事實上兩者都像開關一樣,讓一個裝置與另一個裝置能以通透的方式連接。在此情況下,「通透」指得是使用交互連線時,不需要透過特別的 API 程式;而不是「不耗費任何成本」的意思。
因為系統架構的差異性極大,因此要歸類存取非本地記憶體的效能耗損並不實際。我們可以說,每個交互連結中的每個「中繼點」(hop)都會讓效能降低些許,因此參照一個離現有 CPU 兩個交互連線遠的記憶體位置時,會增加至少「2N + 記憶體週期時間」的存取時間,其中 N 是每個中繼點所耗去的時間。
考慮到此效能耗損,對效能極其敏感的應用程式應避免在 NUMA 拓樸系統上,定期存取遠端記憶體。這應用程式應該設定為留在特定的節點上,並從該節點上分配記憶體。
要這樣做的話,應用程式必須知道幾件事:
  1. 系統的「拓樸」為何?
  2. 目前應用程式在何處執行?
  3. 最近的記憶體插槽在哪裡?

4.1.2. 微調 CPU 效能

讀取此節能讓使用者了解如何微調,以取得更佳的 CPU 效能,此節也簡介了數種工具以達成此目標。
NUMA 一開始是用來連接單一處理器與多記憶體插槽。自從 CPU 製造商精心打造處理器,並減小晶片大小後,單一 CPU 就可包含多核心。這些 CPU 核心聚集一處,每個都可以用同樣時間存取本機記憶體插槽,核心之間也可以共享快取;然而每次在核心、記憶體、與快取之間「切換」時,都會消耗些許效能。
圖形 4.1, “NUMA 拓樸的本地與遠端記憶體存取” 中的範例系統包含了兩個 NUMA 節點。每個節點都有四個 CPU、一組記憶體插槽、以及一組記憶體控制晶片。節點上的任何 CPU 都可直接存取節點上的記憶體插槽。如節點 1 的箭頭所示,步驟如下:
  1. 一顆處理器(0-3 的任何一顆)將記憶體位址傳給本地的記憶體控制器。
  2. 記憶體控制器設定對記憶體位址的存取。
  3. CPU 對那組記憶體位址進行讀或寫。
The CPU icon used in this image is part of the Nuvola 1.0 (KDE 3.x icon set), and is held under the LGPL-2.1: http://www.gnu.org/licenses/lgpl-2.1.html

圖形 4.1. NUMA 拓樸的本地與遠端記憶體存取

然而,如果一個節點上的某個 CPU 需要存取屬於另一個 NUMA 節點的記憶體插槽,需要採取的路徑就會迂迴些:
  1. 一顆處理器(0-3 的任何一顆)將記憶體位址傳給遠端的記憶體控制器。
    1. 針對遠端記憶體位址的 CPU 需求會傳到遠端的記憶體控制器上,看起來就像從該節點的本機存取節點。
  2. 遠端記憶體控制器設定對遠端記憶體位址的存取。
  3. CPU 對那組遠端記憶體位址進行讀或寫。
每個動作都需要透過多個記憶體控制器,因此遠端存取的時間會是本機的兩倍有餘。因此,多核心系統的主要效能考量,是確保資訊能透過最短或最快的路徑,以最有效率的方式傳輸。
要配置應用程式以取得最佳 CPU 效能,您需要知道:
  • 系統拓樸(系統元件是如何相連的)、
  • 應用程式執行時所使用的核心、以及
  • 最近的記憶體插槽之位置。
RHEL 6 附有多種工具,幫助使用者尋找資訊,並根據這些資訊微調系統。以下章節會對 CPU 效能微調的工具作簡介。

4.1.2.1. 使用 taskset 設定 CPU 關聯

taskset 會透過程序 ID,擷取、設定執行中程序的 CPU 關聯(CPU affinity)。這程式可以用來使用特定的 CPU 關聯來啟動程序,綁定一組特定程序與一個(或多個)特定 CPU。然而,taskset 並不能保證分配本地記憶體。如果您需要分配本地記憶體所帶來的額外效能,我們建議您使用 numactl 而非 taskset;詳情請見〈節 4.1.2.2, “使用 numactl 控制 NUMA 政策”〉。
CPU 關聯是以位元遮罩的方式呈現。最低位元相當於第一個邏輯 CPU,最高位元相當於最後一個邏輯 CPU。通常這些遮罩是以 16 位元方式呈現,因此 0x00000001 表示編號為 0 的處理器,而 0x00000003 表示編號為 0 與 1 的處理器。
要為執行中的程序設定 CPU 關聯,請執行以下指令,並以您想要綁定的一或多顆處理器取代 mask、將您想要改變關聯之程序 ID 取代 pid
# taskset -p mask pid
要使用給定的關聯啟動程序,請執行以下指令,並以您希望程序想要綁定的一或多顆處理器取代 mask、並以您想要執行的程式、選項、與引數取代 program
# taskset mask -- program
您可使用位元遮罩來指定處理器,也可以使用 -c 選項加上以逗號分隔的個別處理器清單(或處理器範圍),例如:
# taskset -c 0,5,7-9 -- myprogram
關於 taskset 的進一步資訊,請參閱 man page,指令為:man taskset

4.1.2.2. 使用 numactl 控制 NUMA 政策

numactl 會使用特定的排程或記憶體取代政策,執行程序。所選擇的政策會套用在該程序與其子程序上。numactl 也可以為共享記憶體區段或檔案,設定一致性的政策;並為程序設定 CPU 關聯與記憶體關聯。它會使用 /sys 檔案系統來決定系統拓樸。
/sys 檔案系統包含了 CPU、記憶體、與周邊裝置是如何透過 NUMA 相互連結的資訊。特別是 /sys/devices/system/cpu 目錄包含了系統 CPU 如何相互連結的資訊。/sys/devices/system/node 目錄包含了系統上的 NUMA 節點的資訊、以及這些節點之間的相對距離。
在 NUMA 系統上,處理器與記憶體插槽的距離愈長,處理器存取記憶體插槽的速度就愈慢。因此,對效能敏感的應用程式應該配置好,從最近的可用記憶體插槽存取記憶體。
對效能敏感的應用程式也應該配置好,在多個核心上執行,特別是會用到多執行續的應用程式更該這麼配置。因為第一級快取多半較小,如果多執行續在同樣的 CPU 核心上執行,每個執行續都可能會將前一個執行續所存取的快取資料逐出。當作業系統試圖在這些執行續之間進行多工作業、同時執行續持續逐出其他執行續的快取資料時,取代快取段(cache line)的執行時間就會大幅增加。這問題稱為「快取置換」(cache thrashing)。因此,建議綁定多執行續應用程式至節點上,而非單一 CPU 核心上,因為這允許執行續在多層級(第一、第二、與最後快取)上共享快取段,並降低填滿快取的運作需求。然而,如果所有執行續都存取同樣的快取資料,綁定應用程式至單一 CPU 核心有其效能上的優勢。
numactl 能讓您綁定應用程式至特定 CPU 核心或 NUMA 節點,並分配記憶體使其與 CPU 核心或多核心與應用程式產生關聯。numactl 的有用選項有:
--show
顯示現有程序的 NUMA 政策設定。這參數並不需要其他參數,用法類似:numactl --show
--hardware
顯示系統上可用節點的清單。
--membind
僅能從特定節點分配記憶體。當使用此選項時,如果這些節點上的記憶體不足,分配運作就會失敗。此參數的用法為 numactl --membind=nodes program,其中 nodes 是您想要分配記憶體的節點清單,program 是向節點索求記憶體之程式。節點的編號可以是用逗號分隔的清單、一段範圍、或是以上二者的結合。numactl 的更多資訊可以在 man page 中找到:man numactl
--cpunodebind
只在屬於特定一或多個節點上的 CPU 執行一個指令(與其子程序)。此參數的用法是 numactl --cpunodebind=nodes program,其中 nodes 是該與特定程式(program)綁定的 CPU 之節點清單。節點的編號可以是用逗號分隔的清單、一段範圍、或是以上二者的結合。numactl 的更多資訊可以在 man page 中找到:man numactl
--physcpubind
僅在特定處理器上執行指令(及其子程序)。此參數的用法是 numactl --physcpubind=cpu program,其中 cpu 是以逗號隔開的實體 CPU 編號,如 /proc/cpuinfo 檔案中的處理器欄位所列,而 program 是僅在這些 CPU 上執行的程式。CPU 也可以透過相對於現有 cpuset 來指定。詳情請參閱 numactl 的 man page,指令為:man numactl.
--localalloc
表示記憶體應該總是分配至現有節點上。
--preferred
可能的話,記憶體會分配至指定的節點。如果記憶體無法分配至指定的節點上,就會改分配到其它節點。這選項只接受單一節點編號,例如:numactl --preferred=node。詳情請參閱 numactl 的 man page,指令為:man numactl
libnuma 函式庫包含在 numactl 套件中,提供了簡單的程式介面給 NUMA 政策使用,並由 kernel 所支援。這函式庫提供了比 numactl 工具程式更多、更精細的微調功能。欲知更多詳情,請參閱 numa 的 man page,指令為:man numa(3)

4.1.3. numastat

重要

之前,numastat 工具是由 Andi Kleen 所寫的 Perl script。現在 RHEL 6.4 使用的版本已經大幅重新撰寫。
為了與之前的版本相容,預設指令(numastat,沒有任何選項或參數)對於相容性採取嚴格的措施;請注意使用此指令並加上選項或參數後,會顯著改變螢幕輸出的內容及其格式。
numastat 顯示了每個 NUMA 節點上,程序與作業系統對記憶體存取的統計資料(例如能否正確分配的數據)。預設上,執行 numastat 會顯示每個節點上,多少記憶體分頁是由以下事件類別所佔據。
numa_missnuma_foreign 值愈低,表示 CPU 的效能愈好。
numastat 的更新版本也能顯示程序的記憶體是否橫跨系統,或是透過 numactl 集中在特定的節點上。
交叉參照 numastat 的結果(透過每個 CPU 的 top 結果)來驗證程序的執行續都在分配記憶體的同樣節點上執行。

預設的追蹤類別

numa_hit
成功分配至此節點的數目。
numa_miss
試圖分配到其它節點,但因為其它節點的記憶體不足而分配至此節點的數目。每個 numa_miss 事件都有相對應至另一個節點的 numa_foreign 事件。
numa_foreign
一開始試圖分配至此節點,但最後卻分配至其它節點的數目。每個 numa_foreign 事件都有相對應至另一個節點的 numa_miss 事件。
interleave_hit
用交錯政策成功分配至此節點的數目。
local_node
此節點上,一組程序成功分配記憶體至此節點的次數。
other_node
此節點上,其它節點上的一組程序分配記憶體至此節點的次數。
使用以下任何選項,都會將記憶體的顯示單位改為 MB(四捨五入至小數點後二位),並改變其它特定的 numastat 行為,如下所述。
-c
水平濃縮資訊表。這在擁有大量 NUMA 節點的系統上非常有用,但欄寬與欄間距難以預測。使用此選項時,記憶體數量會四捨五入至最近的 MB 數。
-m
以每個節點為基礎,顯示系統全域的記憶體使用資訊,與 /proc/meminfo 中的資訊類似。
-n
顯示與原始 numastat 指令所列出的相同資訊(numa_hit、numa_miss、numa_foreign、interleave_hit、local_node、以及 other_node),加上更新的格式,單位為 MB。
-p pattern
根據指定的樣式(pattern),顯示每個節點的記憶體資訊。如果 pattern 的值包括數字,numastat 會假設那是數字型態的程序 ID。反之,numastat 會從程序的命令列搜尋特定的樣式。
-p 選項之後的引數會被視為用來篩選的樣式。額外的樣式會加大篩選條件,而非縮小。
-s
根據 total 欄位,降冪顯示資料,讓最消耗記憶體的程序列在上面。
或者,您可以指定 node(節點),那麼表格就會根據 node 欄位來排序。使用此選項時, node 的值必須緊跟在 -s 選項之後,如以下所示:
numastat -s2
不要在此選項及其值之間,加入任何空白字元。
-v
顯示更詳盡的資料。亦即:多程序的程序資訊會以詳盡的方式列出。
-V
顯示 numastat 的版本資訊。
-z
忽略所顯示資訊中,任何欄、列的零。請注意,一些接近零而被四捨五入為零的值還是會被列出。

4.1.4. NUMA 關聯管理 daemon(numad

numad 是自動的 NUMA 關聯(affinity)管理 daemon,監控系統中的 NUMA 拓樸與資源使用量,以動態改進 NUMA 資源的分配與管理,進而改善系統效能。
根據系統負載,numad 最高能將效能提昇 50%。要達成這目標,numad 會定期從 /proc 檔案系統存取資訊,以節點為單位監控可用的系統資源。接下來 daemon 會試著將重要程序放到擁有足夠記憶體與 CPU 資源的 NUMA 節點上,好將 NUMA 效能最大化。程序管理的現有門檻為單一 CPU 至少 50%,以及至少 300 MB 的記憶體。numad 會試圖維持資源的利用等級,並在需要於 NUMA 節點間移動程序時讓整個分配重新達到平衡。
numad 也提供了預先放置(pre-placement)的建議服務,透過多種工作管理系統,協助程序一開始綁定 CPU 與記憶體資源。不管 numad 是否在系統上以 daemon 的形態執行,預先放置的建議服務都是可用的。詳情請參閱 man page 中的 -w 選項,以了解預先配置的建議,指令為:man numad

4.1.4.1. numad 的好處

numad 主要能加惠擁有長時間執行的程序之系統,這種程序會消耗大量資源,特別是包含在總系統資源中的子集合裡的程序。
numad 也許也能加惠消耗多 NUMA 節點的珍貴資源之應用程式。然而,numad 能提供的好處,會隨著系統上所消耗資源的比例逐漸增高而降低。
一支程式只執行幾分鐘,或不消耗太多資源時,numad 就不太可能會改善效能。擁有持續、不可預期的記憶體存取模式之系統,例如位於記憶體中的大型資料庫,也不太可能得益於 numad

4.1.4.2. 操作模式

注意

大量合併 kernel 記憶體的統計資料後,這些資料也許會相互牴觸。因此,當 KSM daemon 合併大量記憶體時,numad 會感到混淆。然而,如果您的系統擁有大量閒置記憶體,關閉並停用 KSM daemon 會達到更高的效能。
numad 可以以兩種方式執行:
  • 以服務方式執行
  • 以可執行檔方式執行
4.1.4.2.1. 使用 numad 作為服務
numad 服務執行時,會根據系統的負載,動態調整系統。
要啟動服務,請執行:
# service numad start
要讓重新開機之後此服務依舊會執行,請執行:
# chkconfig numad on
4.1.4.2.2. 以執行檔方式執行 numad
要以執行檔方式執行 numad,請執行:
# numad
numad 會一直執行,直到停止為止。執行時的活動都會記錄在 /var/log/numad.log 檔案裡。
要限制 numad 管理特定程序,請以下列選項啟動:
# numad -S 0 -p pid
-p pid
將特定的 pid 明確加入清單中。此處指定的程序只有在達到 numad 程序的顯著門檻時,才會受到管理。
-S mode
-S 參數會指定程序掃描類型。設定為 0 會限制 numad 的管理方式為明確納入程序。
要停止 numad,請執行:
# numad -i 0
停止 numad 並不會移除用來改善 NUMA 關聯之改變。如果系統大幅使用這些改變,再次執行 numad 會在新條件下,調整關聯以改善效能。
欲知 numad 選項的進一步資訊,請參閱 numad 的 man page,指令為:man numad