天天看點

libnuma詳解(A NUMA API for LINUX)一、什麼是NUMA二、帶寬優化三、NUMA的實作四、NUMA POLICIES五、Libnuma程式設計

白皮書

numa3手冊

numa3手冊

一、什麼是NUMA

  1. 關于這個很多部落格都有,基本都是這個白皮書的翻譯或者擴充。

    在傳統的SMP(對稱多處理)系統中,計算機有一個由所有cpu共享的記憶體控制器。當所有處理器同時通路記憶體時,這種單一記憶體連接配接常常成為瓶頸。對于具有更多cpu的大型系統,它也不能很好地擴充。由于這個原因,越來越多的現代系統正在使用CC/NUMA(緩存一緻性/非一緻性記憶體通路)架構。例如AMD* Opteron*、IBM* Power5*、HP* Superdome和SGI* Altix*。

    在SMP系統上,所有cpu都可以平等地通路連接配接到所有記憶體晶片(DIMMs)的同一個共享記憶體控制器。cpu之間的通信也要經過這個共享資源,這可能會造成擁塞。可以由單一控制器管理的記憶體晶片的數量也是有限的,這就限制了系統可以支援多少記憶體。此外,通過這個單一流量集線器通路記憶體的延遲相對較高。

    NUMA體系結構被設計為超越SMP體系結構的可伸縮性限制。系統被分割成多個節點,而不是每台計算機有一個單一的記憶體控制器。每個節點都有處理器和自己的記憶體。處理器可以非常快地通路節點中的本地記憶體。系統中的所有節點都通過快速互連連接配接起來。添加到系統中的每個新節點都為系統提供了更多的聚合記憶體帶寬和容量,進而提供了出色的可伸縮性。

    一個節點中的所有處理器都對該節點中的記憶體具有同等的通路權。在CPU內建了記憶體控制器(如AMD Opteron)的系統上,節點通常由單個CPU組成,可能帶有多個核心或虛拟線程。在其他更傳統的NUMA系統上,如SGI Altix或HP Superdome,具有2到4個cpu的較大節點(類似于小型SMP系統)共享記憶體。

    在NUMA系統中,每個CPU都可以通路本地和遠端記憶體。本地記憶體位于與CPU相同的節點上,提供了非常低的記憶體通路延遲。遠端記憶體位于不同的節點,必須通過互連通路。從軟體的角度來看,這個遠端記憶體可以用相同的方式使用本地記憶體;它是完全緩存相幹的。通路它需要更長的時間,因為互連比節點的本地記憶體總線增加了更多的延遲。

    理論上,NUMA系統可以被視為一個SMP系統,隻要忽略軟體中本地和遠端記憶體之間的差異。事實上,這是經常發生的。但為了獲得最佳性能,應該考慮到這些差異。

    NUMA架構的一個巨大優勢是,即使在一個有許多cpu的大系統中,本地記憶體的延遲也可能非常低。因為現代的CPU比記憶體晶片快得多,是以CPU在從記憶體讀取資料時通常要花費相當長的時間等待。是以,最小化記憶體延遲可以提高軟體性能。

    NUMA政策關心的是将記憶體配置設定放在特定的節點上,以讓程式盡可能快地通路它們。實作這一點的主要方法是為其本地節點上的線程配置設定記憶體,并保持線程在那裡運作(節點關聯)。這為記憶體提供了最佳的延遲,并将通過全局互連的流量最小化。

    在SMP系統上,有一個與之有點類似的常見優化,稱為緩存關聯。緩存關聯試圖将資料儲存在CPU的緩存中,而不是在處理器之間頻繁地彈跳資料。這通常是由作業系統中的排程器完成的,該排程器試圖在将線程排程到另一個CPU之前,将線程在一個CPU上保持一段時間。

    但是,記憶體關聯與節點關聯有一個重要的差別:當SMP系統上的線程在cpu之間移動時,它的緩存内容最終會随之移動。一旦記憶體區域被送出到NUMA系統上的特定節點,它就會保留在那裡。在通路它的不同節點上運作的線程總是向互連添加通信量,并導緻更高的延遲。這就是NUMA系統需要比SMP系統更努力地歸檔節點關聯的原因。當然,緩存關聯本身在NUMA系統上也是值得優化的。要獲得最佳性能,這是不夠的。

    然而,作業系統中的排程器不能總是僅針對節點關聯進行優化。問題是,在系統中不使用CPU會比使用遠端記憶體并看到更高記憶體延遲的程序更糟糕。在記憶體性能甚至比系統中所有cpu的使用更重要的情況下,應用程式或系統管理者可以覆寫作業系統的預設決策。這允許更好地優化特定的工作負載。

    Linux傳統上使用系統調用将線程綁定到特定的cpu(使用sched_set_affinity(2)系統調用和schedutils)。NUMA API擴充了這一點,允許程式指定應該在哪個節點上配置設定記憶體。

    為了使使用者空間程式更容易優化NUMA配置,API可以導出拓撲資訊,并允許使用使用者指定的處理器和記憶體資源。還有一些内部核心api提供供核心子系統使用的NUMA拓撲資訊。

    這裡描述的NUMA API将線程到cpu的位置和記憶體的位置分開。它主要與記憶體的放置有關。此外,應用程式可以單獨配置CPU關聯。

    NUMA API目前可在SUSE®LINUX Enterprise Server 9上用于AMD64和Intel* Itanium*處理器

    族。

  2. 關于NUMA的一些其他了解

    上面白皮書其實寫的已經很好了,但是沒有啥細節,對我這種小白不友好。

    • socket node core cpu thread等概念解讀

      socket是一個實體上的概念,指的是主機闆上的cpu插槽。

      node是一個邏輯上的概念,是相鄰core的一個分組。

      core一般是一個實體cpu,一個獨立的硬體執行單元。

      thread是邏輯的執行單元,一般對應 cpu 的核數。

    • NUMA的拓撲結構介紹

      2-socket Intel NUMA topology。CPU 0和CPU 1表示實體處理器包,而不是單個核心,并且作為記憶體子產品的相應數量的NUMA記憶體節點被部署在對應的處理器的相鄰的DIMM卡槽中。

      libnuma詳解(A NUMA API for LINUX)一、什麼是NUMA二、帶寬優化三、NUMA的實作四、NUMA POLICIES五、Libnuma程式設計
      4-socket Intel E5-4600 NUMA node architecture
      libnuma詳解(A NUMA API for LINUX)一、什麼是NUMA二、帶寬優化三、NUMA的實作四、NUMA POLICIES五、Libnuma程式設計
      4-socket AMD NUMA node architecture。每個實體處理器包都有兩個NUMA節點。
      libnuma詳解(A NUMA API for LINUX)一、什麼是NUMA二、帶寬優化三、NUMA的實作四、NUMA POLICIES五、Libnuma程式設計
      圖4說明了NUMA節點局部性的介紹(其中NUMA節點被認為是給定核心的本地節點)。在本例中,對于實體處理器CPU 0中的core 0,緊挨着該處理器socket的記憶體被認為是本地NUMA節點。對于core 1,它是實體處理器CPU 1的一部分,被認為是本地的NUMA節點是挂在CPU 1上的節點。每個實體處理器最多可以有8個實體核心和Intel Xeon E5-2600系列處理器,每個socket最多可以有16個邏輯處理器(啟用超線程)。為了便于示範,隻顯示了每個處理器socket上的第一個實體核心。
      libnuma詳解(A NUMA API for LINUX)一、什麼是NUMA二、帶寬優化三、NUMA的實作四、NUMA POLICIES五、Libnuma程式設計
      圖5表示了另一種可視化相對NUMA節點局部性的方法。在這裡,我們來看另一個簡化的示例,其中每個位于socket(表示為CPU 0和CPU 1)中的top-bin E5-2600系列實體處理器都有8個核。出于本示例的目的,每個處理器核都從1到8進行編号,盡管在啟動後核心編号政策會發生變化。我們可以看到CPU 0上的第一個核(用綠色表示)是NUMA節點0的本地核。這意味着最靠近CPU 0填充的DIMM插槽是本地的,而最靠近CPU 1填充的DIMM插槽(紅色的NUMA node 1)是遠端的。這是因為要從CPU 0上的核心1到達NUMA節點1,記憶體請求必須周遊CPU間QPI鍊路,并使用CPU 1的記憶體控制器來通路這個遠端節點。額外的跳躍增加了遠端NUMA節點記憶體通路的延遲。
      libnuma詳解(A NUMA API for LINUX)一、什麼是NUMA二、帶寬優化三、NUMA的實作四、NUMA POLICIES五、Libnuma程式設計
      我們看到了相同的系統,但是現在Linux已經列出了核心。注意,每個核心現在都表示為一個不同的OS CPU,這是Linux引用邏輯處理器的方式。還需要注意的是,第一個OS CPU(0)是第一個實體處理器包上的第一個核心。現在注意,OS CPU 1是第二個實體處理器包上的第一個核心。這種模式在所有可用的核心上都延續,是以對于Intel 2P系統,所有偶數編号的OS cpu都在第一個實體處理器包上,而所有奇數編号的OS cpu都在第二個實體處理器包上。同樣重要的是要注意,對于任何特定的NUMA節點,哪些核心是本地的,以及OS cpu和NUMA節點是從0開始編号的。
      libnuma詳解(A NUMA API for LINUX)一、什麼是NUMA二、帶寬優化三、NUMA的實作四、NUMA POLICIES五、Libnuma程式設計
      圖9中表示的核心枚舉模式顯示了同一個啟用超線程的2套接字處理器Intel xeon系統。字元前的第一個數字表示配置設定給實際核心的OS CPU号。字元後面的第二個數字表示配置設定給超線程同級線程的OS CPU數字。在Linux核心引導的核心枚舉階段,首先在填充的實體處理器包之間以循環的方式枚舉所有真正的核心。一旦枚舉了所有的真實核,超線程的兄弟核也會以類似的方式枚舉,在填充的處理器包之間循環。對于一個帶有Intel Xeon 8核處理器的2P 16核系統的示例,在啟用超線程時總共枚舉了32個邏輯處理器。
      libnuma詳解(A NUMA API for LINUX)一、什麼是NUMA二、帶寬優化三、NUMA的實作四、NUMA POLICIES五、Libnuma程式設計
      注意,每個實體處理器包都有兩個與之關聯的NUMA節點。此外,每個實體處理器被分成兩組,每組8個核。與單個NUMA節點關聯的核心是按順序枚舉的。在圖11中,我們看到OS CPU 0 8是NUMA節點0的一部分。同一實體處理器包上編号為OS CPU 9 15的第二組核心對于NUMA節點1來說是本地的。需要注意的是,轉移到下一個套接字需要從32開始枚舉核心。第四個實體處理器包使用OS CPU 16啟動核心枚舉并繼續進行
      libnuma詳解(A NUMA API for LINUX)一、什麼是NUMA二、帶寬優化三、NUMA的實作四、NUMA POLICIES五、Libnuma程式設計

二、帶寬優化

程式的記憶體通路性能可以根據延遲或帶寬進行優化。大多數程式似乎更喜歡低延遲,但也有一些例外需要帶寬。

使用節點本地記憶體具有最佳的延遲。為了獲得更大的帶寬,可以并行使用多個節點的存儲控制器。這類似于RAID如何通過将I/O操作分散到多個硬碟上來提高磁盤I/O性能。NUMA API可以使用CPU中的MMU(記憶體管理單元)來交錯來自不同記憶體控制器的記憶體塊。這意味着這樣一個映射中的每個連續的page都來自不同的節點。

當一個應用程式對這樣的交錯區域做一個大的流記憶體通路時,多個節點的記憶體控制器的帶寬被合并。它工作的好壞取決于NUMA體系結構,特别是取決于互連的性能以及本地和遠端記憶體之間的延遲差異。

在某些系統上,它隻對相鄰節點的子集有效。

一些NUMA系統,如AMD Opteron,可以通過固件配置,在頁面基礎上交錯所有節點的所有記憶體。這叫做節點交錯。節點交錯類似于NUMA API提供的交錯模式,但它們在重要方面有所不同。節點交錯适用于所有記憶體。可以為每個程序或線程單獨配置NUMA API交錯。如果固件啟用了節點交織,則NUMA政策将被禁用。要使用NUMA政策,必須在BIOS或固件中始終禁用節點交錯。

使用NUMA API,每個應用程式都可以單獨調整記憶體區域用于延遲或帶寬的政策。

三、NUMA的實作

NUMA政策是由幾個子系統共同努力提供的。核心管理程序或特定記憶體映射的記憶體政策。這個核心可以通過三個新的系統調用來控制。可以從應用程式中調用一個名為libnuma的使用者空間共享庫。推薦使用libnuma API 來使程式實作NUMA政策。它提供了比直接使用系統調用更友好和抽象的接口。本文僅描述這個進階接口。

當不應該修改應用程式時,管理者可以使用numactl指令行實用程式設定一些政策。這不如直接從應用程式控制政策靈活

使用者庫和應用程式包含在numactl RPM中,它是SUSE LINUX Enterprise Server 92的一部分。此外,包中還有一些實用程式,如numastat,用于收集關于記憶體配置設定的統計資訊,numademo用于顯示不同政策對系統的影響。這個包還包含所有函數和程式的手冊頁。

四、NUMA POLICIES

NUMA記憶體政策是 NUMA-aware(NUMA感覺)應用程式 可以利用的程式設計接口。

NUMA API的主要任務是管理政策。政策可以應用于程序或記憶體區域。

NUMA API目前支援四種政策:

政策名字 描述
default 在本地節點(目前線程運作的節點)上進行配置設定
bind 在特定的節點集上配置設定
interleave 在一組節點上交錯配置設定記憶體
preferred 試着先在一個節點上配置設定

bind和preferred之間的差別是,當不能在指定的節點上配置設定記憶體時,bind會失敗;而preferred則傳回到其他節點。使用bind可能會導緻更早的記憶體短缺和由于交換而導緻的延遲。在libnuma中,preferred和bind是結合在一起的,可以通過numa_set_strict libnuma函數對每個線程進行更改。預設的是更靈活的preferred配置設定。

可以為每個程序(程序政策)或每個記憶體區域設定政策。子程序繼承fork上父程序的程序政策。程序政策應用于程序上下文中進行的所有記憶體配置設定。這包括在系統調用和檔案緩存中進行的内部核心配置設定。中斷總是在目前節點上配置設定。當核心配置設定記憶體頁時,程序政策總是适用。

為每個記憶體區域設定政策,也稱為VMA政策3,允許程序為其位址空間中的記憶體塊設定政策。記憶體區域政策比程序政策具有更高的優先級。記憶體區域政策的主要優點是可以在配置設定發生之前設定它們。目前隻支援匿名程序記憶體、SYSV共享記憶體、shmem和tmpfs映射以及大型tlbfs檔案。共享記憶體的區域政策一直持續到删除共享記憶體段或檔案為止。

NUMACTL的部分跳過。
           

五、Libnuma程式設計

1. 基礎:檢查NUMA

libnuma是一個可以連結到程式的共享庫,它為NUMA政策提供了一個穩定的API。它提供了比直接使用NUMA API系統調用更進階别的接口,是程式推薦的接口。libnuma是numactl RPM的一部分。

應用程式連結libnuma如下:

CC …… -lnuma

NUMA API函數和宏在 “numa.h” 包含檔案中聲明。

#include <numa.h>
   ……
   if(numa_available() < 0) {
   	printf("Your system does not support NUMA API\n");
   	...
   }
   ……
           

在使用任何NUMA API函數之前,程式必須調用numa_available()。當該函數傳回一個負值時,系統上不支援NUMA政策。在這種情況下,所有其他NUMA API函數的行為都是未定義的,不應該調用它們。

下一步通常是調用numa_max_node()。這個函數發現并傳回系統中的節點數。節點的數量通常需要在程式中設定和驗證記憶體政策。所有的程式都應該動态地發現這個問題,而不是寫死一個特定的系統拓撲。

每個線程都在本地儲存所有libnuma狀态。更改一個線程中的政策不會影響程序中的其他線程。

下面幾節通過一些示例概述各種libnuma函數。一些不常見的功能沒有提到。要獲得更詳細的參考資料,請參閱numa(3)手冊頁。

2. nodemasks

libnuma以在numa.h中定義的稱為nodemask_t的抽象資料類型管理節點集。nodemask_t是節點編号的固定大小位集。系統中的每個節點都有一個唯一的編号。最大的數字是numa_max_node()傳回的數字。最高的節點 是根據 常數NUMA_NUM_NODES 的實作定義的。nodemask通過引用傳參數給許多NUMA API函數。

numa.h中nodemask_t的定義:

#if defined(__x86_64__) || defined(__i386__)
#define NUMA_NUM_NODES  128
#else
#define NUMA_NUM_NODES  2048
#endif

typedef struct {
        unsigned long n[NUMA_NUM_NODES/(sizeof(unsigned long)*8)];
} nodemask_t;
           

可以看到 nodemask_t 的大小是 NUMA_NUM_NODES 除 unsigned long 的位數。

NUMA_NUM_NODES 的宏定義邏輯我沒看懂,再說。

nodemask用nodemask_zero()初始化為空。

nodemask_t mask;
nodemask_zero(&mask);
           

單個節點可以用nodemask_set設定,用nodemask_clr清除。nodemask_equal比較兩個節點。nodemask_isset測試是否在nodemask中設定了位。

nodemask_set(&mask, maxnode); /* set node highest */
if (nodemask_isset(&mask, 1)) { /* is node 1 set? */
...
}
nodemask_clr(&mask, maxnode); /* clear highest node again */
           

有兩個預定義的節點 : numa_all_nodes 表示系統中的所有節點,numa_no_nodes 是空集。

3. 簡單記憶體空間配置設定

libnuma提供了使用指定政策配置設定記憶體的函數。這些配置設定函數将所有配置設定配置設定到頁面(在AMD64系統上為4 KB),并且相對較慢。它們應該僅用于配置設定超出CPU緩存大小的大型記憶體對象,以及NUMA政策可能會提供幫助的地方。當不能配置設定記憶體時,它們傳回NULL。所有由numa_alloc函數族配置設定的記憶體都應該通過numa_free釋放。

numa_alloc_onnode在特定節點上配置設定記憶體:

void *mem = numa_alloc_onnode(MEMSIZE\_IN\_BYTES, 1);
if (mem == NULL)
/* report out of memory error */
... pass mem to a thread bound to node 1 ...
           

memsize應該小于節點大小。請記住,其他程式也可能在該節點上配置設定記憶體,而配置設定整個節點可能導緻交換。下面描述的numa_node_size()函數可用于自動發現目前系統的節點大小限制。建議為管理者提供一種方法來覆寫程式的自動選擇并限制記憶體消耗。

線程最終必須使用numa_free釋放記憶體:

預設情況下,numa_alloc_onnode首先嘗試在指定的節點上配置設定記憶體,但是當記憶體不足時,就會傳回到其他節點。當numa_set_strict(1) 首先執行時,當目标節點上沒有足夠的記憶體時,它不會後退并導緻配置設定失敗。在此之前,核心會嘗試交換節點上的記憶體并清除其他緩存,這可能會導緻延遲。

numa_alloc_interleaved在系統中的所有節點上交錯配置設定記憶體。

void *mem = numa_alloc_interleaved(MEMSIZE\_IN\_BYTES);
if (mem == NULL)
/* report out of memory error */
... run memory bandwidth intensive algorithm on mem ...
numa\_free(mem, MEMSIZE_IN_BYTES);
           

在所有節點上交錯使用記憶體并不總是性能上的優勢。根據機器的NUMA架構,有時将程式交錯在相鄰節點的一個子集上可以獲得更好的帶寬。

numa_alloc_interleaved_子集函數隻能用于交錯特定的一組節點。

另一個函數是numa_alloc_local,它在本地節點上配置設定記憶體。這通常是所有配置設定的預設值,但是當程序有不同的程序政策時,顯式地指定是有用的。numa_alloc使用目前程序政策配置設定記憶體。

4. 程序政策

每個線程都有一個從父線程繼承的預設記憶體政策。除非用numactl進行了更改,否則該政策通常最好用于在目前節點上配置設定記憶體。當不能修改程式中的現有代碼來直接使用前一節中描述的numa_alloc函數時,有時更改程式中的程序政策是有用的。通過這種方式,特定子函數可以使用非預設政策運作,而無需實際修改它們的代碼。程序政策還可用于在啟動子程序之前為其設定政策。

numa_set_interleave_mask允許對目前線程進行交錯。所有将來的記憶體配置設定都是通過交錯指定的節點請求來配置設定記憶體的。傳遞numa_all_nodes會将記憶體交錯給所有節點。傳 numa_no_nodes再次關閉交叉。numa_get_interleave_mask傳回目前交錯掩碼。這對于在庫中更改狀态之前儲存狀态,以便稍後恢複狀态非常有用。

numamask_t oldmask = numa_get_interleave_mask();
numa_set_interleave_mask(&numa_all_nodes);
/* run memory bandwidth intensive legacy library that allocates memory */
numa_set_interleave_mask(&oldmask);
           

numa_set_preferred設定目前線程的首選節點。記憶體配置設定器嘗試首先在該節點上配置設定記憶體。如果沒有足夠的空閑記憶體,它将傳回到其他節點。

numa_set_membind将嚴格的記憶體綁定掩碼設定為nodemask。“嚴格”意味着必須在指定的節點上配置設定記憶體。當在交換後沒有足夠的記憶體可用時,配置設定失敗。

numa_get_membind傳回目前的記憶體綁定掩碼。

numa_set_localalloc将程序政策設定為标準的本地配置設定政策。

5. 改變已配置設定記憶體區域政策

當使用共享記憶體時,通常不可能使用numa_alloc函數家族來配置設定記憶體。記憶體必須從shmat()或mmap擷取。為了允許libnuma程式在這些區域上設定政策,有一些附加函數用于為已經存在的記憶體區域設定記憶體政策。

這些函數隻影響指定區域的未來配置設定。Linux使用請求分頁,并且隻在CPU第一次通路某個頁面時配置設定記憶體。

numa_interleave_memory使用一個交織掩碼設定一個交織政策。将numa_all_nodes傳遞給系統中的所有節點。

void *mem = shmat( ... ); /* get shared memory */
numa_interleave_mask(mem, size, numa_all_nodes);
           

numa_tonode_memory配置設定特定節點上的記憶體。numa_tonodemask_memory将記憶體放到節點掩碼中。numa_setlocal_memory為目前節點配置設定記憶體區域提供了一個政策。numa_police_memory使用目前政策來配置設定記憶體。這在以後更改記憶體政策時非常有用。

當先前執行numa_set_strict(1)時,這些調用會在記憶體區域中任何已經存在的頁面不符合新政策時調用numa_error。否則,現有頁面将被忽略。

6. 綁定到CPUs

到目前為止所讨論的函數在特定節點上配置設定記憶體。NUMA政策的另一部分是在正确節點的cpu上運作線程。這是由numa_run_on_node函數完成的,該函數将目前線程綁定到節點中的所有cpu。numa_run_on_node_mask将線程綁定到一個nodemask中的所有cpu。

在節點1上運作目前線程并配置設定記憶體:

numa_run_on_on_node(1);
numa_set_prefered(1);
           

使用libnuma的一種簡單方法是numa_bind函數。它将将來配置設定給特定nodemask的程序的CPU和記憶體綁定在一起。它與前面的示例相同。

使用numa_bind将程序CPU和記憶體配置設定綁定到節點1:

nodemask_t mask;
nodemask_zero(&mask);
nodemask_set(&mask 1);
numa_bind(&mask);
           

通過将線程綁定到numa_all_nodes,可以允許線程再次在所有節點上執行:

numa_get_run_node_mask函數傳回允許目前線程運作的節點的nodemask。這可用于在運作子程序或啟動線程之前儲存和恢複排程器關聯狀态。

7. 關于環境的保證

numa_node_size傳回一個節點的記憶體大小。傳回參數是它的記憶體的總大小,這些記憶體不一定都是程式可用的。第二個參數是一個指針,可以用節點上的空閑記憶體填充它。程式可以在空閑記憶體(空閑記憶體通常很低,因為Linux使用空閑記憶體進行緩存)和最大記憶體大小之間配置設定節點記憶體。

在配置設定記憶體時,Linux釋放緩存的檔案資料,但是配置設定過多的記憶體可能導緻交換。這個函數給出了每個節點上有多少記憶體可供配置設定的提示,但是應該隻将其作為提示,最好是通過某種方式讓管理者重寫這個函數。一般來說,預設情況下建議永遠不要配置設定超過節點總記憶體一半的記憶體,除非管理者指定了更多。

uma_node_to_cpus傳回一個節點中CPUs的CPU數量。這可以用來确定一個節點中有多少個cpu。作為參數,它擷取節點編号和數組指針。最後一個參數是數組的位元組長度。該數組由CPU數字的位掩碼填充。例如,稍後可以将這些CPU編号傳遞給sched_set_affinity系統調用。當數組不夠長不能包含所有cpu時,函數傳回-1并将errno設定為ERANGE。建議應用程式處理此錯誤或傳遞一個非常大的緩沖區,比如512位元組。否則,在非常大的機器上可能會出現故障。Linux已經在1024台CPU機器上運作,預計将被轉移到更大的機器上。

8. 錯誤處理

libnuma中的錯誤處理相對簡單。造成這種情況的主要原因是設定NUMA政策時的錯誤通常可以忽略。一個錯誤的NUMA政策的最壞結果是程式運作得比它本可以運作的更慢。

當設定政策時發生錯誤時,将調用numa_error函數。預設情況下,它向stderr列印一個錯誤。當設定了numa_exit_on_error全局變量時,它将退出程式。函數被聲明為弱函數,可以通過在主程式中定義替換函數來重寫。例如,一個c++程式可以在那裡抛出一個c++異常。

當沒有可用記憶體時,記憶體配置設定函數總是傳回NULL。

9. NUMASTAT工具統計NUMA記憶體配置設定資料

對于系統中的每個節點,核心在配置設定每個頁面時維護一些與NUMA配置設定狀态相關的統計資訊。此資訊可能對測試NUMA政策的有效性有用。

統計資訊是通過numastat指令檢索的。統計資訊是按每個節點收集的。在每個節點有多個CPU核心的系統上,numastat聚合一個節點上所有核心的結果,進而形成整個節點的單個結果。numastat指令報告每個節點的以下統計資訊:

統計資訊種類 解釋
numa_hit 當程序從特定節點請求頁面并從被請求節點接收頁面時,該特定節點的numa_hit将增加。這個程序可能在系統中的任何節點上運作,不一定在本節點運作。
numa_miss 當程序從特定節點請求頁面,但是卻從其他節點接收頁面時,numa_miss會在實際配置設定頁面的節點上增加。 這個程序可能在系統中的任何節點上運作,不一定在本節點運作。
numa_foreign 當程序從特定節點請求頁面,但是卻從其他節點接收頁面時,numa_foreign在請求頁面的原始節點上遞增。這個程序可能在系統中的任何節點上運作,不一定在本節點運作。
interleave_hit 當某個頁面的配置設定遵循位址範圍的interleave政策時,interleave_hit在配置設定頁面的節點上遞增。此外,numa_hit和local_node或other_node都會在配置設定頁面的節點上增加。不儲存根據交錯政策配置設定的頁面(除了儲存在被請求節點上的,因為該節點缺少空閑頁面)的統計資訊。No statistics are kept for pages allocated according to the interleave policy but not on the requested node because of its lack of free pages.
local_node 當程序請求一個頁面,并且結果頁面位于程序運作的相同節點時,local_node将在該特定節點上遞增。
other_node 當程序請求一個頁面,并且結果頁面位于與程序運作的節點不同的節點時,對于實際配置設定頁面的節點,other_node會增加。

numa_miss和numa_hit以及local_node和foreign_node之間的差別是,前兩個對NUMA政策的命中或未命中進行計數。後者計算配置設定是否與請求線程在同一個節點上。

為了更好了解這些 NUMASTAT 統計量,舉幾個例子:

  1. 運作在節點0上的程序請求節點0上的一個頁面,并在節點0上配置設定該頁面::

     node  3  node  2  node  1  node  0  numa_hit  + 1  numa_miss   numa_foreign   interleave_hit   local_node  + 1  other_node  \begin{array}{llllll} & \text { node } 3 & \text { node } 2 & \text { node } 1 & \text { node } 0\\ \text { numa\_hit } & & & & +1\\ \text { numa\_miss } & & & & \\ \text { numa\_foreign } & & & \\ \text { interleave\_hit } & & & \\ \text { local\_node } & & & & +1\\ \text { other\_node } & & & & \end{array}  numa_hit  numa_miss  numa_foreign  interleave_hit  local_node  other_node ​ node 3​ node 2​ node 1​ node 0+1+1​

  2. 運作在節點0上的程序請求節點0上的一個頁面,但是由于節點0上的空閑頁面不足,它被配置設定在節點1上.

     node  3  node  2  node  1  node  0  numa_hit   numa_miss  + 1  numa_foreign  + 1  interleave_hit   local_node   other_node  + 1 \begin{array}{llllll} & \text { node } 3 & \text { node } 2 & \text { node } 1 & \text { node } 0\\ \text { numa\_hit } & & & & \\ \text { numa\_miss } & & & +1& \\ \text { numa\_foreign } & & & &+1 \\ \text { interleave\_hit } & & & \\ \text { local\_node } & & & & \\ \text { other\_node } & & & +1& \end{array}  numa_hit  numa_miss  numa_foreign  interleave_hit  local_node  other_node ​ node 3​ node 2​ node 1+1+1​ node 0+1​

  3. 運作在節點0上的程序請求并接收節點1上的頁面。請注意這個示例與第一個示例之間的差別。

     node  3  node  2  node  1  node  0  numa_hit  + 1  numa_miss   numa_foreign   interleave_hit   local_node   other_node  + 1 \begin{array}{llllll} & \text { node } 3 & \text { node } 2 & \text { node } 1 & \text { node } 0\\ \text { numa\_hit } & & & +1& \\ \text { numa\_miss } & & & & \\ \text { numa\_foreign } & & & & \\ \text { interleave\_hit } & & & \\ \text { local\_node } & & & & \\ \text { other\_node } & & & +1 & \end{array}  numa_hit  numa_miss  numa_foreign  interleave_hit  local_node  other_node ​ node 3​ node 2​ node 1+1+1​ node 0​

  4. 運作在節點0上的程序請求節點1上的一個頁面,但是由于節點1上空閑頁面不足,它被配置設定在節點0上

     node  3  node  2  node  1  node  0  numa_hit   numa_miss  + 1  numa_foreign  + 1  interleave_hit   local_node  + 1  other_node  \begin{array}{llllll} & \text { node } 3 & \text { node } 2 & \text { node } 1 & \text { node } 0\\ \text { numa\_hit } & & & & \\ \text { numa\_miss } & & & &+1 \\ \text { numa\_foreign } & & & +1& \\ \text { interleave\_hit } & & & \\ \text { local\_node } & & & &+1 \\ \text { other\_node } & & & & \end{array}  numa_hit  numa_miss  numa_foreign  interleave_hit  local_node  other_node ​ node 3​ node 2​ node 1+1​ node 0+1+1​

  5. 作為進一步的示例,考慮一個4節點的機器,每個節點有4 GB RAM。最初,numastat報告了這台機器的以下統計資料:

     node  3  node  2  node  1  node  0  numa_hit  58956 142758 424386 319127  numa_miss  0 0 0 0  numa_foreign  0 0 0 0  interleave_hit  19204 20238 19675 20576  local_node  43013 126715 409434 305254  other_node  15943 16043 14952 13873 \begin{array}{llllll} & \text { node } 3 & \text { node } 2 & \text { node } 1 & \text { node } 0\\ \text { numa\_hit } & 58956 & 142758 & 424386 & 319127\\ \text { numa\_miss } & 0 & 0 & 0 &0 \\ \text { numa\_foreign } & 0 & 0 & 0 &0 \\ \text { interleave\_hit } &19204& 20238 &19675& 20576 \\ \text { local\_node } & 43013 & 126715 & 409434 & 305254 \\ \text { other\_node } & 15943 & 16043 & 14952 & 13873 \end{array}  numa_hit  numa_miss  numa_foreign  interleave_hit  local_node  other_node ​ node 35895600192044301315943​ node 2142758002023812671516043​ node 1424386001967540943414952​ node 0319127002057630525413873​

    現在假設一個名為memhog的程式在節點1上運作,在執行期間配置設定了8 GB的RAM。memhog完成後,numastat報告以下統計資料:

     node  3  node  2  node  1  node  0  numa_hit  58956 142758 424386 320893  numa_miss  48365 1026046 0 0  numa_foreign  0 0 1074411 0  interleave_hit  19204 20238 19675 20577  local_node  43013 126856 1436403 307019  other_node  64308 1042089 14952 13873 \begin{array}{llllll} & \text { node } 3 & \text { node } 2 & \text { node } 1 & \text { node } 0\\ \text { numa\_hit } & 58956 & 142758 & 424386 & 320893\\ \text { numa\_miss }& 48365 & 1026046 & 0 & 0 \\ \text { numa\_foreign } & 0 & 0 & 1074411 &0 \\ \text { interleave\_hit } & 19204 & 20238 & 19675 & 20577 \\ \text { local\_node } & 43013 & 126856 & 1436403 & 307019 \\ \text { other\_node } & 64308 & 1042089 & 14952 & 13873 \end{array}  numa_hit  numa_miss  numa_foreign  interleave_hit  local_node  other_node ​ node 358956483650192044301364308​ node 214275810260460202381268561042089​ node 14243860107441119675143640314952​ node 0320893002057730701913873​

    從這裡面的變化可以看出,memhog程式嘗試配置設定來自節點1的1,074,411個頁面,但無法這樣做。相反,該程序 node 2配置設定了1,026,046個頁面,node3 配置設定了48,365個頁面。

    系統調用略過
               

繼續閱讀