coolshell最新的文章《性能调优攻略》在“多核CPU调优”章节,提到“我们不能任由操作系统负载均衡,因为我们自己更了解自己的程序,所以,我们可以手动地为其分配CPU核,而不会过多地占用CPU0,或是让我们关键进程和一堆别的进程挤在一起。”。在文章中提到了Linux下的一个工具,taskset,可以设定单个进程运行的CPU。 同时,因为最近在看redis的相关资料,redis作为单进程模型的程序,为了充分利用多核CPU,常常在一台server上会启动多个实例。而为了减少切换的开销,有必要为每个实例指定其所运行的CPU。
下文,将会介绍taskset命令,以及sched_setaffinity系统调用,两者均可以指定进程运行的CPU实例。 1.taskset taskset是LINUX提供的一个命令(ubuntu系统可能需要自行安装,schedutils package)。他可以让某个程序运行在某个(或)某些CPU上。 以下均以redis-server举例。 1)显示进程运行的CPU 命令taskset -p 21184 显示结果: pid 21184's current affinity mask: ffffff 注:21184是redis-server运行的pid 显示结果的ffffff实际上是二进制24个低位均为1的bitmask,每一个1对应于1个CPU,表示该进程在24个CPU上运行 2)指定进程运行在某个特定的CPU上 命令taskset -pc 3 21184 显示结果: pid 21184's current affinity list: 0-23 注:3表示CPU将只会运行在第4个CPU上(从0开始计数)。 3)进程启动时指定CPU 命令taskset -c 1 ./redis-server ../redis.conf
结合这上边三个例子,再看下taskset的manual,就比较清楚了。 OPTIONS -c, --cpu-list
2.sched_setaffinity系统调用 如下文章部分翻译自:http://www.thinkingparallel.com/2006/08/18/more-information-on-pthread_setaffinity_np-and-sched_setaffinity/ 问题描述 sched_setaffinity可以将某个进程绑定到一个特定的CPU。你比操作系统更了解自己的程序,为了避免调度器愚蠢的调度你的程序,或是为了在多线程程序中避免缓存失效造成的开销,你可能会希望这样做。如下是sched_setaffinity的例子,其函数手册可以参考(http://www.linuxmanpages.com/man2/sched_getaffinity.2.php): /* Short test program to test sched_setaffinity * (which sets the affinity of processes to processors). * Compile: gcc sched_setaffinity_test.c * -o sched_setaffinity_test -lm * Usage: ./sched_setaffinity_test * * Open a "top"-window at the same time and see all the work * being done on CPU 0 first and after a short wait on CPU 1. * Repeat with different numbers to make sure, it is not a * coincidence. */ #include <stdio.h> #include <math.h> #include <sched.h> double waste_time(long n) { double res = 0; long i = 0; while(i <n * 200000) { i++; res += sqrt (i); } return res; } int main(int argc, char **argv) { unsigned long mask = 1; /* processor 0 */ /* bind process to processor 0 */ if (sched_setaffinity(0, sizeof(mask), &mask) <0) { perror("sched_setaffinity"); } /* waste some time so the work is visible with "top" */ printf ("result: %f\n", waste_time (2000)); mask = 2; /* process switches to processor 1 now */ if (sched_setaffinity(0, sizeof(mask), &mask) <0) { perror("sched_setaffinity"); } /* waste some more time to see the processor switch */ printf ("result: %f\n", waste_time (2000)); }
根据你CPU的快慢,调整waste_time的参数。然后使用top命令,就可以看到进程在不同CPU之间的切换。(启动top命令后按“1”,可以看到各个CPU的情况)。
父进程和子进程之间会继承对affinity的设置。因此,大胆猜测,taskset实际上是首先执行了sched_setaffinity系统调用,然后fork+exec用户指定的进程。
假设业务模型中耗费cpu的分四种类型,(1)网卡中断(2)1个处理网络收发包进程(3)耗费cpu的n个worker进程(4)其他不太耗费cpu的进程 基于1中的 负载均衡是针对进程数,那么(1)(2)大部分时间会出现在cpu0上,(3)的n个进程会随着调度,平均到其他多个cpu上,(4)里的进程也是随着调度分配到各个cpu上; 当发生网卡中断的时候,cpu被打断了,处理网卡中断,那么分配到cpu0上的worker进程肯定是运行不了的 其他cpu上不是太耗费cpu的进程获得cpu时,就算它的时间片很短,它也是要执行的,那么这个时候,你的worker进程还是被影响到了;按照调度逻辑,一种非常恶劣的情况是:(1)(2)(3)的进程全部分配到cpu0上,其他不太耗费cpu的进程数很多,全部分配到cpu1,cpu2,cpu3上。。那么网卡中断发生的时候,你的业务进程就得不到cpu了 如果从业务的角度来说,worker进程运行越多,肯定业务处理越快,人为的将它捆绑到其他负载低的cpu上,肯定能提高worker进程使用cpu的时间
每个cpu都利用起来了,负载会比不绑定的情况下好很多 有效果的原因: 依据《linux内核设计与实现》的42节,人为控制一下cpu的绑定还是有用处地 示例程序 cpu.c #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> int main(int argc, char* argv[]){ int num = sysconf(_SC_NPROCESSORS_CONF); int created_thread = 0; int myid; int i; int j = 0; cpu_set_t mask; cpu_set_t get; if (argc != 2) { printf("usage : ./cpu num\n"); exit(1); } myid = atoi(argv[1]); printf("system has %i processor(s). \n", num); CPU_ZERO(&mask); CPU_SET(myid, &mask); if (sched_setaffinity(0,sizeof(mask), &mask) == -1) { printf("warning: could not set CPU affinity, continuing...\n"); } while (1) { CPU_ZERO(&get); if (sched_getaffinity(0, sizeof(get), &get) == -1) { printf("warning: cound not get cpu affinity, continuing...\n"); } for (i = 0; i < num; i++) { if (CPU_ISSET(i, &get)) { printf("this process %d is running processor : %d\n",getpid(), i); } } } return 0; }
下面是在两个终端分别执行了./cpu 0 ./cpu 2 后得到的结果. 效果比较明显. QUOTE: Cpu0 : 5.3%us, 5.3%sy, 0.0%ni, 87.4%id, 0.0%wa, 0.0%hi, 2.0%si, 0.0%st linux下的进程可以通过sched_setaffinity系统调用设置进程亲和力,限定进程只能在某些特定的CPU上运行。负载均衡必须考虑遵守这个限制(前面也多次提到)。 |
cpu_set_t mask; cpu_set_t get; int i; int num = sysconf(_SC_NPROCESSORS_CONF); printf("system has %d processor(s)\n", num); CPU_ZERO(&mask); CPU_SET(1, &mask); //指定运行在哪个CPU上。我们linux机器(Linux version 2.6.32-5-amd64 (Debian 2.6.32-35))有4颗CPU,其编号为0-3 pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask); for (i = 0;; i++) { //此循环导致CPU占用率为100% if (!(i%10000)) { std::vector<int> myvector; for (int i = 0; i < 1000;i++) { myvector.push_back(i); } } }