Windows下繫結執行緒到指定的CPU核心

czwy發表於2024-04-29

在某些場景下,需要把程式繫結到指定CPU核心提高執行效率。透過微軟官方文件查詢到Windows提供了兩個Win32函式:SetThreadAffinityMaskSetProcessAffinityMask 為指定執行緒和程序設定處理器關聯掩碼。通俗的講就是在指定的CPU核心上執行執行緒或者程序。

這裡的CPU核心指的是邏輯核心,而非物理核心。

SetThreadAffinityMask

SetThreadAffinityMask用於設定指定執行緒的處理器關聯掩碼,從而實現執行緒對處理器的繫結。

SetThreadAffinityMask函式定義

SetThreadAffinityMask的定義如下:

DWORD_PTR SetThreadAffinityMask(
  [in] HANDLE    hThread,
  [in] DWORD_PTR dwThreadAffinityMask
);

從函式的定義看需要傳遞兩個引數:

  • hThread:指向要設定處理器關聯的執行緒控制代碼。如果是想設定當前執行緒的關聯掩碼,可以使用 GetCurrentThread() 函式獲取控制代碼。
  • dwThreadAffinityMask:處理器的關聯掩碼。是一個DWORD_PTR型別的值,長度共8個位元組(64bit),每一bit代表一個cpu核。

如果需要支援超過64核的CPU時,則需要使用SetThreadGroupAffinity函式

為了更清晰的表達dwThreadAffinityMask的含義,下邊用二進位制數表示該值。比如,需要把執行緒繫結到
第0個核:則dwThreadAffinityMask=0B_0001;(0x01)
第1個核:則dwThreadAffinityMask=0B_0010;(0x02)
第2個核:則dwThreadAffinityMask=0B_0100;(0x04)
第3個核:則dwThreadAffinityMask=0B_1000;(0x08)
……
如果要繫結到多個cpu核心,比如繫結到第1和2個cpu核時,dwThreadAffinityMask=0B_0110,對應的十六進位制數也就是0x06。

呼叫示例

首先引入Win32API

[DllImport("kernel32.dll")]
static extern UIntPtr SetThreadAffinityMask(IntPtr hThread, UIntPtr dwThreadAffinityMask);

[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentThread();

由於dwThreadAffinityMask的值是按照2n的指數遞增,與通常習慣指定第n個核心不符,並且不同的裝置CPU核心數不一樣,指定CPU核心時可能超出CPU核心數量,因此可以對指定CPU核心做個簡單的處理:

static ulong SetCpuID(int lpIdx)
{
    ulong cpuLogicalProcessorId = 0;
    if (lpIdx < 0 || lpIdx >= System.Environment.ProcessorCount)
    {
        lpIdx = 0;
    }
    //透過移位運算轉換lgidx->dwThreadAffinityMask:0->1,1->2,2->4,3->8,……
    cpuLogicalProcessorId |= 1UL << lpIdx;
    return cpuLogicalProcessorId;
}

接下來就可以進行測試了

ulong LpId = SetCpuID((int)lpIdx);
SetThreadAffinityMask(GetCurrentThread(), new UIntPtr(LpId));

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < 1000000; i++)
{
    for (int j = 0; j < 1000000; j++)
    {
        int _data = j;
    }
}
stopwatch.Stop();
Console.WriteLine("執行時間: " + stopwatch.ElapsedMilliseconds.ToString());

效果如圖如下:
image

SetProcessAffinityMask

SetProcessAffinityMask與SetThreadAffinityMask非常相似,不同的是其作用於整個程序,可以決定程序內的所有執行緒共同執行在指定的處理器上。
函式定義如下:

BOOL SetProcessAffinityMask(
  [in] HANDLE    hProcess,
  [in] DWORD_PTR dwProcessAffinityMask
);

和SetThreadAffinityMask一樣,也是需要傳遞兩個引數,只不過第一個引數傳遞的是程序的控制代碼。

小結

在某些場景可以透過SetThreadAffinityMaskSetProcessAffinityMask 提高程式執行效率,主要是基於以下幾個原因:

  • 提高效能:透過將執行緒繫結到特定的處理器,可以減少執行緒在不同處理器之間的切換開銷,尤其是在多核系統中,有助於提升程式的執行效率。
  • 避免快取抖動:確保執行緒始終在同一個處理器上執行,可以減少快取未命中,因為相關的資料更可能保留在該處理器的快取記憶體中。
  • 實時系統和併發控制:在需要嚴格控制執行緒執行位置的場景下,比如實時系統或者某些併發控制策略中,透過設定處理器關聯可以滿足特定的排程需求。

需要注意的是,SetThreadAffinityMask和SetProcessAffinityMask並不是獨佔CPU核心,如果關聯的CPU核心本身負載就很高,這個時候程式執行效率反而會降低。

相關文章