目录
前言
搜索引擎性能优化中,考虑用cpu绑定提升搜索性能。
CPU领域中最广为人知的一条定律——摩尔定律:预计18个月会将芯片的性能提高一倍。过去几十年,各大公司致力于提高CPU晶体管密度和提高CPU工作频率,使得CPU的性能提升基本符合摩尔定律。但随着工艺不断发展,晶体管密度提升已经接近物理极限,CPU工作频率也由于功耗和发热的制约而无法继续提升。在基础物理领域没有大的突破的前提下,单核CPU的性能提升日益困难,于是,各大公司将目光投向了通过增加CPU核心数提高性能领域,双核、4核、8核、16核等一系列多核CPU相继问世。
怎样合理调度多个CPU核心运行应用程序从而充分利用多核CPU的优势成为热门的研究问题,本文介绍的CPU亲和性便是解决该问题的方法之一。
什么是CPU亲和性?
引用一下维基百科的说法,CPU亲和性就是绑定某一进程(或线程)到特定的CPU(或CPU集合),从而使得该进程(或线程)只能运行在绑定的CPU(或CPU集合)上。CPU亲和性利用了这样一个事实:进程上一次运行后的残余信息会保留在CPU的状态中(也就是指CPU的缓存)。如果下一次仍然将该进程调度到同一个CPU上,就能避免缓存未命中等对CPU处理性能不利的情况,从而使得进程的运行更加高效。
CPU亲和性分为两种:软亲和性和硬亲和性。软亲和性主要由操作系统来实现,Linux操作系统的调度器会倾向于保持一个进程不会频繁的在多个CPU之间迁移,通常情况下调度器都会根据各个CPU的负载情况合理地调度运行中的进程,以减轻繁忙CPU的压力,提高所有进程的整体性能。除此以外,Linux系统还提供了硬亲和性功能,即用户可以通过调用系统API实现自定义进程运行在哪个CPU上,从而满足特定进程的特殊性能需求。
如何将CPU亲和性应用到程序中?
Linux系统中每个进程的task_struct
结构中有一个cpus_allowed
位掩码,该掩码的位数与系统CPU核数相同(若CPU启用了超线程则为核数乘以2),通过修改该位掩码可以控制进程可运行在哪些特定CPU上。Linux系统为我们提供了CPU亲和性相关的调用函数和一些操作的宏定义,函数主要是下面两个:
sched_set_affinity()
(修改位掩码)sched_get_affinity()
(查看当前的位掩码)
除此之外还提供了一些宏定义来修改掩码,如CPU_ZERO()
(将位掩码全部设置为0)和CPU_SET()
(设置特定掩码位为1)。
下面采用一个以@Eli Dow提供的程序为基础修改的程序介绍CPU亲和性的使用方法,该程序使用CPU亲和性API将N(CPU数量)个进程分别绑定到N个CPU上,代码如下:
#include <iostream>
#include <thread>
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/sysinfo.h>
#include<unistd.h>
#define __USE_GNU
#include<sched.h>
#include<ctype.h>
#include<string.h>
#include<pthread.h>
using namespace std;
/* This method will create processes, then bind each to its own cpu. */
void do_cpu_stress(int num_of_process)
{
int created_process = 0;
/* We need a process for each cpu we have... */
while ( created_process < num_of_process - 1 )
{
int mypid = fork();
if (mypid == 0) /* Child process */
{
break;
}
else /* Only parent executes this */
{
/* Continue looping until we spawned enough processes! */ ;
created_process++;
}
}
/* NOTE: All processes execute code from here down! */
cpu_set_t mask;
/* CPU_ZERO initializes all the bits in the mask to zero. */
CPU_ZERO( &mask );
/* CPU_SET sets only the bit corresponding to cpu. */
CPU_SET(created_process, &mask );
/* sched_setaffinity returns 0 in success */
if( sched_setaffinity( 0, sizeof(mask), &mask ) == -1 ){
cout << "WARNING: Could not set CPU Affinity, continuing..." << endl;
}
else{
cout << "Bind process #" << created_process << " to CPU #" << created_process << endl;
}
//do some cpu expensive operation
int cnt = 100000000;
while(cnt--){
int cnt2 = 10000000;
while(cnt2--){
}
}
}
int main(){
int num_of_cpu = thread::hardware_concurrency();
cout << "This PC has " << num_of_cpu << " cpu." << endl;
do_cpu_stress(num_of_cpu);
}
代码编译运行结果如下:
通过ps -eo pid,args,psr
命令查看CPU与进程是否绑定成功:
可以看出,进程号6644的进程为父进程,该进程运行在CPU3上,6645、6646、6647三个子进程分别运行在CPU1、CPU2、CPU3上,可知进程与CPU绑定成功,进程只会运行在绑定的CPU上而不会被操作系统调度到其他CPU上。
thread 线程绑定CPU方法
在Linux上,我们可以使用pthread特定的pthread_setafftinity_np函数。通过设置其亲和性将每个线程固定到单个CPU:
#include <stdio.h>
#include <iostream>
#include <sstream>
#include <fstream>
#include <queue>
#include <map>
#include <thread>
#include <pthread.h>
#include <mutex>
#include <condition_variable>
#include <dirent.h>
#include <errno.h>
#include <unistd.h>
int main(int argc, const char** argv)
{
constexpr unsigned num_threads = 4;
// A mutex ensures orderly access to std::cout from multiplethreads.
std::mutex iomutex;
std::vector<std::thread>threads(num_threads);
for (unsigned i = 0; i <num_threads; ++i)
{
threads[i] = std::thread([&iomutex, i]{std::this_thread::sleep_for(std::chrono::milliseconds(20));
while (1)
{
{
// Use a lexical scope and lock_guard to safely lock the mutexonly
// for the duration of std::cout usage.
std::lock_guard<std::mutex> iolock(iomutex);
std::cout<< "Thread #" << i << ":on CPU " <<sched_getcpu() << "\n";
}
// Simulate important work done by the tread by sleeping for abit...
std::this_thread::sleep_for(std::chrono::milliseconds(900));
}});
// Create a cpu_set_t object representing a set of CPUs. Clear itand mark
// only CPU i as set.
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(i,&cpuset);
int rc =pthread_setaffinity_np(threads[i].native_handle(),sizeof(cpu_set_t), &cpuset);
if (rc != 0) {
std::cerr << "Error calling pthread_setaffinity_np: " << rc << "\n";
}
}
for (auto& t : threads) {
t.join();
}
return 0;
}
运行结果
Thread #0:on CPU 0
Thread #1:on CPU 1
Thread #2:on CPU 2
Thread #3:on CPU 3
Thread #0:on CPU 0
Thread #1:on CPU 1
Thread #3:on CPU 3
Thread #2:on CPU 2
Thread #0:on CPU 0
Thread #1:on CPU 1
Thread #3:on CPU 3
Thread #2:on CPU 2
绑定常用函数
int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);//进程绑定函数
int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t *cpuset);//线程绑定函数
void CPU_ZERO (cpu_set_t *set) /*这个宏对 CPU 集 set 进行初始化,将其设置为空集。*/
void CPU_SET (int cpu, cpu_set_t *set) /*这个宏将 指定的 cpu 加入 CPU 集 set 中*/
void CPU_CLR (int cpu, cpu_set_t *set) /*这个宏将 指定的 cpu 从 CPU 集 set 中删除。*/
int CPU_ISSET (int cpu, const cpu_set_t *set)
/*如果 cpu 是 CPU 集 set 的一员,这个宏就返回一个非零值(true),否则就返回零(false)。一般线程绑定使用*/
int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t *cpuset);
int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize, cpu_set_t *cpuset);
taskset命令
taskset命令,可用于进程的CPU调优,可以把云服务器上运行的某个进程,指定在某个CPU上工作
- 执行以下命令,获取进程状态(以下操作以进程test.sh为例,对应的pid为23989)
-
执行以下命令,查看进程当前运行在哪个CPU上。
例如:taskset -p 23989
-
执行以下命令,指定进程运行在第二个CPU(CPU1)上。
例如:taskset -pc 1 23989
-
说明:
CPU的标号是从0开始的,所以CPU1表示第二个CPU(第一个CPU的标号是0),这样就把应用程序test.sh绑定到了CPU1上运行
-
也可以使用如下命令在启动程序时绑定CPU(启动时绑定到第二个CPU)上。
taskset -c 1 ./test.sh&
参考质料
c++11 thread 线程绑定CPU方法_zfjBIT的专栏-CSDN博客_c++ 线程 绑定cpu
C/C++ 多线程编程/ 绑定CPU_zgcjaxj的博客-CSDN博客_多线程绑定cpu