简单说,就是让进程不要可哪跑,给定一个cpu core的范围,这样可以减少cs上下文切换后cpu cache miss的情况,节省了主存复制到cpu cache的时间。
该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新. http://xiaorui.cc/?p=4542
#nginx worker_processes 8; worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;
另外我们通过ps aux进程的sys内核态时间,也可以看到优化cpu绑定的效果… …
如果在命令行下获取进程的cpu绑定信息?
#xiaorui.cc taskset -cp 118222 pid 118222's current affinity list: 0-15
意思是说,pid绑定了0-15核,可以说默认的配置。
在终端下配置进程cpu绑定。
taskset -cp 0,4 118222 pid 118222's current affinity list: 0-15 pid 118222's new affinity list: 0,4
taskset -c 0 python abc.py
ps -o pid,psr,comm -p 187090 PID PSR COMMAND 187090 7 python
#xiaorui.cc pname="nginx" # for example for pid in (pgrep "{pname}") do [ "{pid}" != "" ] || exit echo "PID:{pid}" for tid in \ (ps --no-headers -ww -p "{pid}" -L -olwp | sed 's// /' | tr -d '\n') do taskset -cp "{tid}" # substitute thread id in place of a process id done done
到此为止,我们说完了如何在终端下查看配置cpu亲和性。 但其实也可以在程序内部实现绑定,当然难度不在于绑定,不管你是c、golang、python,都是通过系统系统的方式来实现绑定的,下面说说CPU亲和性在用户态的使用
linux的CPU亲和性在用户态表现为一个cpu_set_t掩码的形式,用户可以调用两个函数设置和获取掩码:
#define _GNU_SOURCE /* See feature_test_macros(7) */ #include #设置cpu绑定 int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask); #查看进程的cpu绑定情况 int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
sched_setaffinity是设置指定pid亲和性掩码的,mask是传入的参数;sched_getaffinity则是获取指定pid亲和性掩码的,mask是获取的参数。
cpusetsize可以通过sizeof cpu_set_t算出来。cpu_set_t 是一个掩码数组,一共有1024位,每一位都可以对应一个cpu核心,以下宏,都是对这个掩码进行操作的。如果需要,一个进程是可以绑定多个cpu的。
而mask的表现是如此的:如果是0X23,转换成二进制则为00100011,则表明进程绑定在0核、1核和5核上。
绑核需要注意是,子进程会继承父进程的绑核关系。
那么python多进程内部如何实现cpu绑定?当然你如果不想这么折腾,完全可以在外部做cpu绑定。
python在3.4之后在os模块里加入了cpu_affinity方法,但是参数不太友好,pid绑定的cpu核是掩码,而不是taskset那样易懂的核心数。
推荐使用该模块,https://github.com/algodirect/affinity/blob/master/affinity/src/affinity/__init__.py
他比python3.4内置的os.sched_setaffinity 多了这么一个参数及返回值的处理。
# xiaorui.cc def sched_setaffinity(pid_, cpus_): # same name as in python3 cpu_set_ = cpu_set(cpus_) result_ = set_affinity(pid_, ctypes.sizeof(cpu_set), cpu_set_) if result_ != 0: errno_ = errno_location().contents.value; raise OSError(errno_, os.strerror(errno_)) def sched_getaffinity(pid_): # same name as in python3 cpu_set_ = cpu_set() result_ = get_affinity(pid_, ctypes.sizeof(cpu_set), cpu_set_) if result_ != 0: errno_ = errno_location().contents.value; raise OSError(errno_, os.strerror(errno_)) return cpu_set_.to_list() # 参数转换 class cpu_set(ctypes.Structure): #cpu掩码 _fields_ = [("bits", ctypes.c_ulong * (CPU_SET_SIZE / SZ_LONG))] # to represent cpu_set_t from sched.h def __init__(self, cpus_ = None): for i_ in xrange(CPU_SET_SIZE / SZ_LONG): self.bits[i_] = 0 if cpus_: for cpu_id_ in cpus_: self.enable(cpu_id_) def enable(self, cpu_id_): if cpu_id_ < 0 or cpu_id_ >= NO_OF_CPU: raise ValueError, "Invalid CPU id %d, it doesn't exist", cpu_id_ self.bits[cpu_id_ / SZ_LONG] |= 1 << (cpu_id_ % SZ_LONG) def is_enabled(self, cpu_id_): if cpu_id_ < 0 or cpu_id_ >= NO_OF_CPU: raise ValueError, "Invalid CPU id %d, it doesn't exist", cpu_id_ return ((self.bits[cpu_id_ / SZ_LONG]) & (1 << (cpu_id_ % SZ_LONG))) != 0 def to_list(self): return [ i_ for i_ in range(NO_OF_CPU) if self.is_enabled(i_) ] os.sched_setaffinity = sched_setaffinity # 补丁方法 os.sched_getaffinity = sched_getaffinity # 补丁方法
如果你是python2.7 可以使用Pyobject实现的cpu affinity模块,一老外写的… 简单看了下实现的代码,通过Python.h构建方法,通过include sched.h系统调用。
https://pypi.python.org/pypi?%3Aaction=search&term=affinity&submit=search
那么,如何在项目里合理给进程池配置cpu亲和性绑定?
我这边的进程管理模型是参考nginx master worker原理实现的。 但是跟nginx 工作进程区别在于,nginx的worker是一种工作类型,处理的逻辑是一模一样。既然都一样了,那么nginx可以按照worker pid list顺序的绑定cpu核心。 但我这边的架构是worker分组的,也就是说,有很多种的worker group,每种worker的cpu消耗不一致。
总结:
用python构建的高频服务还是少数,在python里推荐大家使用多进程加协程池模型,该模型配置cpu亲和性是有效果的…. 还有cpu亲和度绑定是否有必要,看你的项目了。
END