Crane
Search
Loading
分类
随机文章
标签云
ArchLinux
Earth
Fringe
Gtalk
Internet
LFS
Love
RegEx
STL
Trick
VHDL
abs
c
c++
code
css
echofon
firefox
fun
g++
game
gcc
geek
google
grep
hack
linux
math
md5
nginx
php
program
python
reader
script
sed
shell
tcpdump
usaco
vim
vimperator
virus
wikipedia
windows
二进制
位运算
危机边缘
哥德尔
大牛
希尔伯特
数据结构
日期
时间
星期五
正则表达式
漫画
生活
电影
程序员
算法
维基
编程
网络
美剧
菜鸟
越狱
输入法
黑色
最新评论
链接
功能
从nginx说cpu affinity
众所周知,nginx是高性能web server的代表,看nginx的代码,随处可以发现对性能的考究,像建立数据结构考虑到cpu的cache line size,比较字符串4个字节转换成整数比较等,这里我们说一下cpu affinity(或cpu亲和)。nginx一般推荐是有几个cpu核,就设置几个worker,这样可以减少进程调度的开销,从而充分地利用cpu。再设置下worker_cpu_affinity,则会把worker绑定在相应的cpu上,从而让worker进程固定在一个cpu上执行,减少切换cpu的cache miss,能获得更好的性能。
这是nginx文档中介绍 worker_cpu_affinity 的部分
syntax: worker_cpu_affinity cpumask ...;default: —context: mainBinds worker processes to the sets of CPUs. Each CPU set is represented by a bitmask of allowed CPUs. There should be a separate set defined for each of the worker processes. By default, worker processes are not bound to any specific CPUs.For example,worker_processes 4;worker_cpu_affinity 0001 0010 0100 1000;binds each worker process to a separate CPU, whileworker_processes 2;worker_cpu_affinity 0101 1010;binds the first worker process to CPU0/CPU2, and the second worker process to CPU1/CPU3. The second example is suitable for hyper-threading.The directive is only available on FreeBSD and Linux.
看nginx的实现,相关内容在源文件os/unix/ngx_setaffinity.c中
void ngx_setaffinity(uint64_t cpu_affinity, ngx_log_t *log) { cpu_set_t mask; ngx_uint_t i; ngx_log_error(NGX_LOG_NOTICE, log, 0, "sched_setaffinity(0x%08Xl)", cpu_affinity); CPU_ZERO(&mask); i = 0; do { if (cpu_affinity & 1) { CPU_SET(i, &mask); } i++; cpu_affinity >>= 1; } while (cpu_affinity); if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) == -1) { ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "sched_setaffinity() failed"); } }
略去上面代码中的nginx_log_t等其它不相关内容,这里主要的操作就是sched_setaffinity。上面nginx文档中有说这个特性仅在FreeBSD和linux上有效,如果系统是FreeBSD,调用的是cpuset_setaffinity;如果系统是linux,调用的是sched_setaffinity(就是上面代码中展示的)。
以linux为例,man sched_setaffinity看一下,系统调用原型是
int sched_setaffinity(pid_t pid, size_t cpusetsize,cpu_set_t *mask);
pid是进程id,一般填0,表示改变当前进程的cpu affinity。
cpu_set_t,指示要设置的cpu affinity,类似select中的fd_set和FD系列宏,这个cpu_set_t的操作也有操作宏,具体可参见man CPU_SET。
cpu_set_t的定义见/usr/include/bits/sched.h /* Size definition for CPU sets. */ # define __CPU_SETSIZE 1024 # define __NCPUBITS (8 * sizeof (__cpu_mask)) /* Type for array elements in 'cpu_set_t'. */ typedef unsigned long int __cpu_mask; /* Data structure to describe CPU mask. */ typedef struct { __cpu_mask __bits[__CPU_SETSIZE / __NCPUBITS]; } cpu_set_t;
可以看到是用bit位来表示cpu的状态的,宏定义写了支持1024个cpu,一般应该是足够使用了。另外对cpu_set_t的操作宏CPU_SET实际的定义也在/usr/include/bits/sched.h中,也就是对__cpu_mask的一些位操作。
/* Access functions for CPU masks. */ # define __CPU_ZERO_S(setsize, cpusetp) \ do { \ size_t __i; \ size_t __imax = (setsize) / sizeof (__cpu_mask); \ __cpu_mask *__bits = (cpusetp)->__bits; \ for (__i = 0; __i < __imax; ++__i) \ __bits[__i] = 0; \ } while (0) # endif # define __CPU_SET_S(cpu, setsize, cpusetp) \ (__extension__ \ ({ size_t __cpu = (cpu); \ __cpu / 8 < (setsize) \ ? (((__cpu_mask *) ((cpusetp)->__bits))[__CPUELT (__cpu)] \ |= __CPUMASK (__cpu)) \ : 0; })) # define __CPU_CLR_S(cpu, setsize, cpusetp) \ (__extension__ \ ({ size_t __cpu = (cpu); \ __cpu / 8 < (setsize) \ ? (((__cpu_mask *) ((cpusetp)->__bits))[__CPUELT (__cpu)] \ &= ~__CPUMASK (__cpu)) \ : 0; })) # define __CPU_ISSET_S(cpu, setsize, cpusetp) \ (__extension__ \ ({ size_t __cpu = (cpu); \ __cpu / 8 < (setsize) \ ? ((((const __cpu_mask *) ((cpusetp)->__bits))[__CPUELT (__cpu)] \ & __CPUMASK (__cpu))) != 0 \ : 0; }))
再结合nginx文档中的例子和nginx的源码来看
worker_processes 4;
worker_cpu_affinity 0001 0010 0100 1000;
如果这个内容写的nginx的配置文件中,然后nginx启动或者重新加载配置的时候,看到worker_process是4,就会起4个worker,然后把worker_cpu_affinity后面的四个值当作四个cpu affinity mask,分别调用ngx_setaffinity,然后就把四个worker进程分别绑定到cpu 0-3上。
如果手头没有nginx,上面的代码就不能在nginx上直观的看到,我们可以自己写个代码来验证一下。
#define _GNU_SOURCE #include <sched.h> #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> int print_cpu_affinity() { int i = 0; cpu_set_t cpu_mask; if(sched_getaffinity(0, sizeof(cpu_set_t), &cpu_mask) == -1) { printf("get_cpu_affinity fail, %s\n", strerror(errno)); return -1; } printf("cpu affinity: "); for(i = 0; i < CPU_SETSIZE; i++) { if(CPU_ISSET(i, &cpu_mask)) printf("%d ", i); } printf("\n"); return 0; } int set_cpu_affinity(uint32_t cpu_affinity) { int i = 0; cpu_set_t cpu_mask; CPU_ZERO(&cpu_mask); for(i = 0;cpu_affinity; i++, cpu_affinity >>= 1) { if(cpu_affinity & 1) { CPU_SET(i, &cpu_mask); } } if(sched_setaffinity(0, sizeof(cpu_set_t), &cpu_mask) == -1) { printf("set_cpu_affinity fail, %s\n", strerror(errno)); return -1; } return 0; } void hold() { while(1) { ; } } int main(int argc, char **argv) { uint32_t cpu_affinity = 10; if( argc < 2 ) { printf("no affinity given, use cpu_affinity = 10\n"); } else { cpu_affinity = strtoul(argv[1], NULL, 2); } print_cpu_affinity(); set_cpu_affinity(cpu_affinity); print_cpu_affinity(); hold(); return 0; }
编译运行
$ cc cpu_affinity.c -o cpu_affinity $ ./cpu_affinity 100 cpu affinity: 0 1 2 3 4 5 6 7 cpu affinity: 2 $ mpstat -P ALL 1 Average: CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle Average: all 12.51 0.00 0.00 0.00 0.00 0.00 0.06 0.00 0.00 87.43 Average: 0 0.00 0.00 0.17 0.00 0.00 0.00 0.00 0.00 0.00 99.83 Average: 1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 Average: 2 99.50 0.00 0.00 0.00 0.00 0.00 0.50 0.00 0.00 0.00 Average: 3 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 Average: 4 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 Average: 5 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 Average: 6 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00 Average: 7 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 100.00
可以看到cpu affinity设置成功了,进程占用了整个cpu2。
设置cpu affinity还有对接口 pthread_getaffinity_np和pthread_setaffinity_np ,这是在 sched_setaffinity这个系统调用的基础上封装的,用线程的时候可以考虑用这个。
用fork产生的child会继承父进程的cpu affinity,进程中产生的也会继承主进程的cpu affinity。
taskset是一个单独的工具,可以设置cpu affinity,既可以在程序启动的时候指定,也可以设置正在运行的进程的cpu affinity。
用taskset来启动我们上面这个程序
# taskset -c 3,5-7 ./cpu_affinity 100 cpu affinity: 3 5 6 7 cpu affinity: 2
可以看到,程序启动的时候的cpu affinity就是-c参数设置的,然后又被我们改成2了。
对于正在运行的进程,可以这样,还是以上面的程序为例
# pgrep cpu_affinity 11353 # taskset -p -c 3,5-7 11353 pid 11353's current affinity list: 2 pid 11353's new affinity list: 3,5-7
可以看到,这个程序之前被我们设置成在cpu2上运行,被taskset改成了3,5-7了。