在某些场景下,需要把程序绑定到指定CPU核心提高执行效率。通过微软官方文档查询到Windows提供了两个Win32函数:SetThreadAffinityMask和SetProcessAffinityMask 为指定线程和进程设置处理器关联掩码。通俗的讲就是在指定的CPU核心上执行线程或者进程。
这里的CPU核心指的是逻辑核心,而非物理核心。
SetThreadAffinityMask的定义如下:
DWORD_PTR SetThreadAffinityMask(
[in] HANDLE hThread,
[in] DWORD_PTR dwThreadAffinityMask
);
从函数的定义看需要传递两个参数:
如果需要支持超过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的值是按照$2^n$的指数递增,与通常习惯指定第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与SetThreadAffinityMask非常相似,不同的是其作用于整个进程,可以决定进程内的所有线程共同运行在指定的处理器上。
函数定义如下:
BOOL SetProcessAffinityMask(
[in] HANDLE hProcess,
[in] DWORD_PTR dwProcessAffinityMask
);
和SetThreadAffinityMask一样,也是需要传递两个参数,只不过第一个参数传递的是线程的句柄。
在某些场景可以通过SetThreadAffinityMask和SetProcessAffinityMask 提高程序执行效率,主要是基于以下几个原因:
在WPF Samples中有一个关于数据绑定到方法的Demo,该Demo结构如下: 运行效果如下所示: 来看看是如何实现的。 先来看下MainWindow.xaml中的内容: