0%

程序绑定 CPU 核心

有时候需要将程序绑定到固定 CPU 的某个核心上运行。

我们知道多核 CPU 系统中,进程和线程的运行在哪个核心是由操作系统内核根据一定的调度算法进行调度的。但是实际软件开发过程中,我们出于一些目的,想要进程或者线程稳定运行在某个 CPU 核心上。比如我想测试两种算法的性能,因为服务器上有一些其他的进程干扰,测试的时间总是有波动,此时就需要将测试程序稳定在某个核心上测试。

Linux 中有 CPU 亲和性这种说法(Windows 有没有我不知道,也不关心)。引用一下维基百科的说法:

CPU 亲和性就是绑定某一进程(或线程)到特定的 CPU(或 CPU 集合),从而使得该进程(或线程)只能运行在绑定的 CPU(或 CPU 集合)上。CPU 亲和性利用了这样一个事实:进程上一次运行后的残余信息会保留在 CPU 的状态中(也就是指 CPU 的缓存)。如果下一次仍然将该进程调度到同一个 CPU 上,就能避免缓存未命中等对 CPU 处理性能不利的情况,从而使得进程的运行更加高效。

Linux 系统中每个进程的 task_struct 结构中有一个 cpus_allowed 位掩码,该掩码的位数与系统CPU 核数相同(若 CPU 启用了超线程则为核数乘以 2),通过修改该位掩码可以控制进程可运行在哪些特定 CPU 上。Linux 系统为我们提供了 CPU 亲和性相关的调用函数和一些操作的宏定义。

Linux 提供了一些宏定义来修改掩码,如 CPU_ZERO() (将位掩码全部设置为 0)和CPU_SET()(设置特定掩码位为 1)。CPU 的亲合力掩码用一个 cpu_set_t 结构体来表示一个 CPU 集合,下面的几个宏分别对这个掩码集进行操作:

  • CPU_ZERO():清空一个集合。
  • CPU_SET()CPU_CLR() 分别对将一个给定的 CPU 号加到一个集合或者从一个集合中去掉。
  • CPU_ISSET() 检查一个 CPU 号是否在这个集合中。

然后还有两个接口帮助我们绑定进程到某个 CPU 或者 CPU 集合上。

  • sched_setaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask)
    • 该函数设置进程pid 的这个进程,让它运行在 mask 所设定的 CPU 上。如果 pid 的值为 0,则表示指定的是当前进程,使当前进程运行在 mask 所设定的那些 CPU 上。第二个参数 cpusetsize 是 mask 所指定的数的长度。通常设定为 sizeof(cpu_set_t)。如果当前 pid 所指定的进程此时没有运行在 mask 所指定的任意一个 CPU 上,则该指定的进程会从其它 CPU 上迁移到 mask 的指定的一个 CPU 上运行。
  • sched_getaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask)
    • 该函数获得 pid 所指示的进程的 CPU 位掩码,并将该掩码返回到 mask 所指向的结构中。即获得指定 pid 当前可以运行在哪些 CPU 上。同样,如果 pid 值为0,也表示的是当前进程。

因此,一个简易常见的将当前进程绑定到 CPU 某个核心(比如 6,CPU ID 从 0 开始)的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
cpu_set_t mask;
cpu_set_t get;
CPU_ZERO(&mask);
int cpu_id = 6;
CPU_SET(cpu_id, &mask);
if (sched_setaffinity(0sizeof(mask), &mask) == -1)
{
std::cout << "WARNING: Could not set CPU Affinity, continuing。。。" << std::endl;
}
else
{
std::cout << "Bind process to cpu id: " << cpu_id << std::endl;
}

Linux 还提供了线程绑定核心的接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int threadBindCPU(std::thread &thread, int cpuID)
{
unsigned num_cpus = std::thread::hardware_concurrency();
std::cout << "Launching " << num_cpus << " threads\n";
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(cpuID, &mask);

int rc = pthread_setaffinity_np(thread.native_handle(), sizeof(cpu_set_t), &mask);
if (rc != 0)
{
std::cerr << "Error calling pthread_setaffinity_np: " << rc << "\n";
}
std::cout << "thread id: " << std::hash<std::thread::id>{}(thread.get_id()) << ", cpu id: " << cpuID << std::endl;
}

查看某个线程绑定的 CPU 核心,需要在线程内部调用:

1
sched_getcpu()