在某些場景下,需要把程式繫結到指定CPU核心提高執行效率。透過微軟官方文件查詢到Windows提供了兩個Win32函式:SetThreadAffinityMask和SetProcessAffinityMask 為指定執行緒和程序設定處理器關聯掩碼。通俗的講就是在指定的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());
效果如圖如下:
SetProcessAffinityMask
SetProcessAffinityMask與SetThreadAffinityMask非常相似,不同的是其作用於整個程序,可以決定程序內的所有執行緒共同執行在指定的處理器上。
函式定義如下:
BOOL SetProcessAffinityMask(
[in] HANDLE hProcess,
[in] DWORD_PTR dwProcessAffinityMask
);
和SetThreadAffinityMask一樣,也是需要傳遞兩個引數,只不過第一個引數傳遞的是程序的控制代碼。
小結
在某些場景可以透過SetThreadAffinityMask和SetProcessAffinityMask 提高程式執行效率,主要是基於以下幾個原因:
- 提高效能:透過將執行緒繫結到特定的處理器,可以減少執行緒在不同處理器之間的切換開銷,尤其是在多核系統中,有助於提升程式的執行效率。
- 避免快取抖動:確保執行緒始終在同一個處理器上執行,可以減少快取未命中,因為相關的資料更可能保留在該處理器的快取記憶體中。
- 實時系統和併發控制:在需要嚴格控制執行緒執行位置的場景下,比如實時系統或者某些併發控制策略中,透過設定處理器關聯可以滿足特定的排程需求。
需要注意的是,SetThreadAffinityMask和SetProcessAffinityMask並不是獨佔CPU核心,如果關聯的CPU核心本身負載就很高,這個時候程式執行效率反而會降低。