本文最后更新于:2024年1月14日 晚上

将不同任务分配至不同计算资源有利于资源合理分配,降低冲突,如果使用得当还可能提升系统性能,本文记录 C++ 实现 CPU 资源分配的方法。

简介

一个程序,当运算很充分(IO等操作很少)时,指定到单独一个CPU上运行会比不指定CPU运行时快。这中间主要有两个原因:

  1. CPU切换时损耗的性能。
  2. Intel的自动降频技术和windows的机制冲突:windows有一个功能是平衡负载,可以将一个线程在不同时间分配到不同CPU,从而使得每一个CPU不“过累”。然而,Inter又有一个技术叫做SpeedStep,当一个CPU没有满负荷运行时自动降频从而达到节能减排的目的。这两个功能实际是冲突的:一个程序被分配到多个CPU协同工作->每个CPU都不是满载->每个CPU都会降频->windows发现每个CPU性能都降低了,因此程序执行速度也降低了。

因此,将线程(进程)绑定到指定CPU核心,从而不让windows自作主张帮我们分散任务,从而提高单线程效率是很有必要的。

而在 C++ 编程中可以实现任务的 CPU 分配。

C++ 实现 CPU 分配

进程分配 CPU 资源

核心函数为 setProcessAffinityMask, 为指定进程的线程设置处理器关联掩码,官方文档

语法
1
2
3
4
BOOL SetProcessAffinityMask(
[in] HANDLE hProcess,
[in] DWORD_PTR dwProcessAffinityMask
);
参数
1
[in] hProcess

要设置其关联掩码的进程句柄。 此句柄必须具有 PROCESS_SET_INFORMATION 访问权限。 有关详细信息,请参阅 进程安全性和访问权限

1
[in] dwProcessAffinityMask

进程的 CPU 关联掩码。

在具有 64 个以上的处理器的系统上,关联掩码必须在单个 处理器组中指定处理器

关联掩码

关联掩码 表示的就是对应 CPU 逻辑核,此处以我的本机为例:

  • 处理器型号: i7-8700 6核 12线程,12个逻辑核

也就是有 12 个 CPU 可以用于分配,掩码用一个二进制比特位表示一个 CPU 是否启用,1为启用,0为禁用

即如果想配置第 $n$(0-11)个CPU,那么就在掩码的第 $n+1$ 个比特位置 1,即加上 $2^n$

  • 我当前想使用第 0,3, 6,9 块 CPU 工作
  • 配置掩码:$Mask = 2^0 + 2^3 + 2^6 + 2^9$

示例代码:

1
2
3
DWORD dwProcessAffinityMask = 1 + 8 + 64 + 512;
int res = SetProcessAffinityMask(GetCurrentProcess(), dwProcessAffinityMask);
// do something

可以看到进程确实控制我安排的目标 CPU 在努力工作,表示分配成功。

返回值

如果该函数成功,则返回值为非零值。

如果函数失败,则返回值为零。 要获得更多的错误信息,请调用 GetLastError。

如果进程相关性掩码请求未在系统中配置的处理器,则最后一个错误代码 ERROR_INVALID_PARAMETER

在具有 64 个以上的处理器的系统上,如果调用进程包含多个处理器组中的线程,则最后一个错误代码 ERROR_INVALID_PARAMETER

注解

进程相关性掩码是位向量,其中每个位表示允许进程线程运行的逻辑处理器。 进程相关性掩码的值必须是 GetProcessAffinityMask 函数获取的系统相关性掩码值的子集。 仅允许在配置为系统的处理器上运行进程。 因此,当系统关联掩码为该处理器指定 0 位时,进程相关性掩码不能为处理器指定 1 位。

进程相关性由任何子进程或新实例化本地进程继承。

请勿在 DLL 中调用 SetProcessAffinityMask ,该 DLL 可能由你自己的进程调用。

在具有 64 个以上的处理器的系统上, SetProcessAffinityMask 函数可用于仅针对单个 处理器组中具有线程的进程设置进程相关性掩码。

使用 SetThreadAffinityMask 函数为多个组中的各个线程设置相关性掩码。 这实际上改变了进程的组分配。

获取进程分配掩码

核心函数为 getProcessAffinityMask,检索指定进程的进程关联掩码和系统的系统相关性掩码。

语法
1
2
3
4
5
BOOL GetProcessAffinityMask(
[in] HANDLE hProcess,
[out] PDWORD_PTR lpProcessAffinityMask,
[out] PDWORD_PTR lpSystemAffinityMask
);
参数
1
[in] hProcess

需要关联掩码的进程句柄。

此句柄必须具有 PROCESS_QUERY_INFORMATIONPROCESS_QUERY_LIMITED_INFORMATION 访问权限。 有关详细信息,请参阅 进程安全性和访问权限

Windows Server 2003 和 Windows XP: 句柄必须具有 PROCESS_QUERY_INFORMATION 访问权限。

1
[out] lpProcessAffinityMask

指向接收指定进程的关联掩码的变量的指针。

1
[out] lpSystemAffinityMask

指向接收系统的关联掩码的变量的指针。

返回值

如果函数成功,则返回值为非零值,并且函数将 lpProcessAffinityMasklpSystemAffinityMask 指向的变量设置为适当的关联掩码。

在具有 64 个以上的处理器的系统上,如果调用进程的线程位于单个处理器组中,该函数会将 lpProcessAffinityMasklpSystemAffinityMask 指向的变量设置为进程关联掩码以及该组的活动逻辑处理器的处理器掩码。 如果调用进程包含多个组中的线程,则函数将返回这两个关联掩码的零。

如果函数失败,则返回值为零,并且 lpProcessAffinityMasklpSystemAffinityMask 指向的变量的值是未定义的。 要获得更多的错误信息,请调用 GetLastError

线程分配 CPU 资源

核心函数为 setThreadAffinityMask , 设置指定线程的处理器相关性掩码,官方文档

语法

C++复制

1
2
3
4
DWORD_PTR SetThreadAffinityMask(
[in] HANDLE hThread,
[in] DWORD_PTR dwThreadAffinityMask
);
参数
1
[in] hThread

要设置关联掩码的线程的句柄。

此句柄必须具有 THREAD_SET_INFORMATIONTHREAD_SET_LIMITED_INFORMATION 访问权限和 THREAD_QUERY_INFORMATIONTHREAD_QUERY_LIMITED_INFORMATION 访问权限。 有关详细信息,请参阅 线程安全和访问权限

Windows Server 2003 和 Windows XP: 句柄必须具有 THREAD_SET_INFORMATIONTHREAD_QUERY_INFORMATION 访问权限。

1
[in] dwThreadAffinityMask

线程的关联掩码。

在具有 64 个以上的处理器的系统上,地缘掩码必须在线程的当前 处理器组中指定处理器

返回值

如果函数成功,则返回值为线程的上一个关联掩码。

如果函数失败,则返回值为零。 要获得更多的错误信息,请调用 GetLastError。

如果线程相关性掩码请求未为进程关联掩码选择的处理器,则最后一个错误代码 ERROR_INVALID_PARAMETER

注解

线程关联掩码是一个位向量,其中每个位表示允许线程在其中运行的逻辑处理器。 线程关联掩码必须是线程包含进程的进程相关性掩码的子集。 线程只能在其进程可以运行的处理器上运行。 因此,当进程相关性掩码为该处理器指定 0 位时,线程关联掩码不能为处理器指定 1 位。

为进程或线程设置关联掩码可能会导致线程接收的处理器时间较少,因为系统被限制在特定处理器上运行线程。 在大多数情况下,最好让系统选择可用的处理器。

如果新的线程关联掩码未指定当前正在运行该线程的处理器,则会在允许的处理器之一上重新计划该线程。

参考资料



文章链接:
https://www.zywvvd.com/notes/coding/cpp/cpp-cpu-task/cpp-cpu-task/


“觉得不错的话,给点打赏吧 ୧(๑•̀⌄•́๑)૭”

微信二维码

微信支付

支付宝二维码

支付宝支付

C++ 为进程、线程分配 CPU 资源
https://www.zywvvd.com/notes/coding/cpp/cpp-cpu-task/cpp-cpu-task/
作者
Yiwei Zhang
发布于
2023年3月17日
许可协议