cpu affinity

2012年3月11日 发表评论 阅读评论 10,472 次浏览

cpu affinity,即cpu亲和性,简单点说就是让某一段代码/数据尽量在同一个或几个cpu核心上长时间运行/计算的机制。为什么需要这样的机制?之前说过,最直观的好处就是能够大大提高cpu cache的命中率,提高性能,而更多的好处无需多说,在用到时自然能够体会到。
利用glibc库中的sched_getaffinity接口,我们获取应用程序当前的cpu亲和性,而通过sched_setaffinity接口则可以把应用程序绑定到固定的某个或某几cpu上运行。这两个接口的定义如下:

#include <sched.h>

int sched_setaffinity(pid_t pid, unsigned int cpusetsize,
                      cpu_set_t *mask);

int sched_getaffinity(pid_t pid, unsigned int cpusetsize,
                      cpu_set_t *mask);

void CPU_CLR(int cpu, cpu_set_t *set);
int CPU_ISSET(int cpu, cpu_set_t *set);
void CPU_SET(int cpu, cpu_set_t *set);
void CPU_ZERO(cpu_set_t *set);

其中的cpu_set_t结构体的具体定义在glibc库的头文件/usr/include/bits/sched.h内:

# define __CPU_SETSIZE	1024
# define __NCPUBITS	(8 * sizeof (__cpu_mask))

/* Type for array elements in 'cpu_set'.  */
typedef unsigned long int __cpu_mask;

typedef struct
{
  __cpu_mask __bits[__CPU_SETSIZE / __NCPUBITS];
} cpu_set_t;

可以看到其用每一bit位表示一个cpu的状态,最多可以表示1024 cpu的亲和状态,这在目前来说足够用了,另外的几个宏CPU_CLR\CPU_ISSET\CPU_SET\CPU_ZERO定义也都定义在头文件/usr/include/bits/sched.h内:

/* Access functions for CPU masks.  */
# define __CPU_ZERO(cpusetp) \
  do {									      \
    unsigned int __i;							      \
    cpu_set_t *__arr = (cpusetp);					      \
    for (__i = 0; __i < sizeof (cpu_set_t) / sizeof (__cpu_mask); ++__i)      \
      __arr->__bits[__i] = 0;						      \
  } while (0)
# define __CPU_SET(cpu, cpusetp) \
  ((cpusetp)->__bits[__CPUELT (cpu)] |= __CPUMASK (cpu))
# define __CPU_CLR(cpu, cpusetp) \
  ((cpusetp)->__bits[__CPUELT (cpu)] &= ~__CPUMASK (cpu))
# define __CPU_ISSET(cpu, cpusetp) \
  (((cpusetp)->__bits[__CPUELT (cpu)] & __CPUMASK (cpu)) != 0)
#endif

利用这几个宏方便我们操作指定cpu的对应bit位,比如清零,置位等。看一个完整的demo程序:

/**
 * FileName: affinity_demo.c
 */
#define _GNU_SOURCE 

#include <stdint.h> 
#include <stdio.h>
#include <sched.h>
#include <pthread.h>
#include <stdlib.h>

#define app_panic(format, args...) \
	do {	\
		printf(format, ## args);	\
		abort();	\
	} while(0)

static inline void print_cpu_mask(cpu_set_t cpu_mask)
{
	uint8_t flag;
	uint32_t i;
	printf("Cpu affinity is ");
	flag = 0;
	for (i = 0; i < sizeof(cpu_set_t); i ++) {
		if (CPU_ISSET(i, &cpu_mask)) {
			if (flag == 0) {
				flag = 1;
				printf("%d", i);
			} else {
				printf(",%d", i);
			}
		}
	}
	printf(".\n");
}

static inline void get_cpu_mask(pid_t pid, cpu_set_t *mask) {
	if (sched_getaffinity(pid, sizeof(cpu_set_t), mask) == -1) {
		app_panic("Get cpu affinity failed.\n");
	}
}

static inline void set_cpu_mask(pid_t pid, cpu_set_t *mask) {
	if (sched_setaffinity(pid, sizeof(cpu_set_t), mask)
			== -1) {
		app_panic("Set cpu affinity failed.\n");
	}
}

int main(int argc, char *argv[])
{
	uint32_t active_cpu = 0;
	cpu_set_t cpu_mask;

	get_cpu_mask(0, &cpu_mask);
	print_cpu_mask(cpu_mask);

	CPU_ZERO(&cpu_mask);
	CPU_SET(active_cpu, &cpu_mask);
	set_cpu_mask(0, &cpu_mask);

	get_cpu_mask(0, &cpu_mask);
	print_cpu_mask(cpu_mask);

	while(1);
	return 0;
}

编译执行它:

[root@localhost affinity]# uname -a
Linux localhost.localdomain 2.6.38.8 #4 SMP Mon Oct 31 20:49:48 CST 2011 x86_64 x86_64 x86_64 GNU/Linux
[root@localhost affinity]# gcc affinity_demo.c -o affinity_demo
[root@localhost affinity]# ./affinity_demo
Cpu affinity is 0,1,2,3.
Cpu affinity is 0.

程序卡死在while死循环这,别终止它,我们另开一个终端看看cpu使用率:

[root@localhost ~]# mpstat -P ALL 1
Linux 2.6.38.8 (localhost.localdomain) 	02/03/2012 	_x86_64_	(4 CPU)

02:07:39 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
02:07:40 AM  all   37.50    0.00    0.00    0.00    0.00    0.00    0.00    0.00   62.50
02:07:40 AM    0  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
02:07:40 AM    1    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
02:07:40 AM    2    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
02:07:40 AM    3    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00

的确,0号cpu占用率为百分之百,而其它cpu完全空闲。再试试把活动cpu设置为1的情况:

[root@localhost affinity]# sed  -e 's/active_cpu = 0/active_cpu = 1/' affinity_demo.c > affinity_demo_1.c
[root@localhost affinity]# diff affinity_demo.c affinity_demo_1.c 
52c52
< 	uint32_t active_cpu = 0;
---
> 	uint32_t active_cpu = 1;
[root@localhost affinity]# gcc affinity_demo_1.c -o affinity_demo_1
[root@localhost affinity]# ./affinity_demo_1 
Cpu affinity is 0,1,2,3.
Cpu affinity is 1.

[root@localhost ~]# mpstat -P ALL 1
Linux 2.6.38.8 (localhost.localdomain) 	02/03/2012 	_x86_64_	(4 CPU)

02:13:56 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
02:13:57 AM  all   35.84    0.00    0.00    0.00    0.00    0.00    0.00    0.00   64.16
02:13:57 AM    0    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
02:13:57 AM    1  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
02:13:57 AM    2    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
02:13:57 AM    3    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00

值得注意的是,cpu affinity会被传递给子线程,例如:

/**
 * FileName: thread_affinity_demo.c
 */
#define _GNU_SOURCE 

#include <stdint.h> 
#include <stdio.h>
#include <sched.h>
#include <pthread.h>
#include <stdlib.h>

#define app_panic(format, args...) \
	do {	\
		printf(format, ## args);	\
		abort();	\
	} while(0)

static inline void print_cpu_mask(cpu_set_t cpu_mask)
{
	uint8_t flag;
	uint32_t i;
	printf("Cpu affinity is ");
	flag = 0;
	for (i = 0; i < sizeof(cpu_set_t); i ++) {
		if (CPU_ISSET(i, &cpu_mask)) {
			if (flag == 0) {
				flag = 1;
				printf("%d", i);
			} else {
				printf(",%d", i);
			}
		}
	}
	printf(".\n");
}

static inline void get_cpu_mask(pid_t pid, cpu_set_t *mask) {
	if (sched_getaffinity(pid, sizeof(cpu_set_t), mask) == -1) {
		app_panic("Get cpu affinity failed.\n");
	}
}

static inline void set_cpu_mask(pid_t pid, cpu_set_t *mask) {
	if (sched_setaffinity(pid, sizeof(cpu_set_t), mask)
			== -1) {
		app_panic("Set cpu affinity failed.\n");
	}
}

void *thread_func(void *param)
{
	cpu_set_t cpu_mask;
	get_cpu_mask(0, &cpu_mask);
	printf("Slave thread ");
	print_cpu_mask(cpu_mask);
	while(1);
}

int main(int argc, char *argv[])
{
	uint32_t active_cpu = 0;
	cpu_set_t cpu_mask;
	pthread_t thread;
	void *thread_ret;

	get_cpu_mask(0, &cpu_mask);
	print_cpu_mask(cpu_mask);

	CPU_ZERO(&cpu_mask);
	CPU_SET(active_cpu, &cpu_mask);
	set_cpu_mask(0, &cpu_mask);

	get_cpu_mask(0, &cpu_mask);
	printf("Master thread ");
	print_cpu_mask(cpu_mask);

	if (pthread_create(&thread, NULL, thread_func, NULL) != 0) {
        perror("pthread_create failed.\n");
    }
	pthread_join(thread, &thread_ret);

	return 0;
}

[root@localhost affinity]# gcc thread_affinity_demo.c -o thread_affinity_demo -lpthread
[root@localhost affinity]# ./thread_affinity_demo 
Cpu affinity is 0,1,2,3.
Master thread Cpu affinity is 0.
Slave thread Cpu affinity is 0.

当然,我们可以在子线程主函数thread_func再设置cpu 亲和性:

void *thread_func(void *param)
{
	cpu_set_t cpu_mask;
	get_cpu_mask(0, &cpu_mask);
	printf("Slave thread ");
	print_cpu_mask(cpu_mask);

	CPU_ZERO(&cpu_mask);
	CPU_SET(1, &cpu_mask);
	CPU_SET(2, &cpu_mask);
	set_cpu_mask(0, &cpu_mask);
	get_cpu_mask(0, &cpu_mask);
	printf("Slave thread ");
	print_cpu_mask(cpu_mask);
	
	while(1);
}
[root@localhost affinity]# gcc thread_affinity_demo_1.c -o thread_affinity_demo_1 -lpthread
[root@localhost affinity]# ./thread_affinity_demo_1
Cpu affinity is 0,1,2,3.
Master thread Cpu affinity is 0.
Slave thread Cpu affinity is 0.
Slave thread Cpu affinity is 1,2.

[root@localhost ~]# mpstat -P ALL 1
Linux 2.6.38.8 (localhost.localdomain) 	02/03/2012 	_x86_64_	(4 CPU)

02:51:01 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
02:51:02 AM  all   31.25    0.00    0.00    0.00    0.00    0.00    0.00    0.00   68.75
02:51:02 AM    0    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
02:51:02 AM    1  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
02:51:02 AM    2    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
02:51:02 AM    3    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
^C

看到的cpu利用率为何只有1号cpu为百分之百?这里是因为线程的执行代码太简单了,只有一个空while死循环,而且当前系统也很空闲,即便是分配了两个cpu,进程调度程序也根本就没去调度它,所以它就随机的在某一个cpu上固定的死耗。当然,如果有其它程序要使用cpu1,那么此种情况下thread_affinity_demo_1就可能会被调度到cpu2上去执行。可以试试,开两个终端都执行thread_affinity_demo_1,此时看到的情况就是这样了:

[root@localhost ~]# mpstat -P ALL 1
Linux 2.6.38.8 (localhost.localdomain) 	02/03/2012 	_x86_64_	(4 CPU)

03:01:42 AM  CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest   %idle
03:01:43 AM  all   49.88    0.00    0.00    0.00    0.00    0.00    0.00    0.00   50.12
03:01:43 AM    0    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00
03:01:43 AM    1  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
03:01:43 AM    2  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
03:01:43 AM    3    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00

在上面调用sched_getaffinity和sched_setaffinity时,我们传递的第一个参数pid都为0,这意味着修改的亲和性就是针对当前调用该函数的线程,这也是最方便的,大多数情况下都这么用,除非你确实想修改其它线程的cpu亲和性。
还有两个api接口pthread_getaffinity_np、pthread_setaffinity_np,和上面介绍的两个api没什么差别,因为它们无非就是对sched_getaffinity和sched_setaffinity的做了一层封装(可以从http://www.gnu.org/software/libc/下载源码查看),以便更适合NPTL使用。简言之,在利用NPTL创建出来的线程代码里,为了更好的兼容性,建议使用pthread_getaffinity_np和pthread_setaffinity_np,此时第一个参数不能再传0,可改成pthread_self()即可。而在其它情况下,当然还是使用sched_getaffinity和sched_setaffinity。

#define _GNU_SOURCE
#include <pthread.h>

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);

Compile and link with -pthread.

参考资料:
http://www.gnu.org/software/libc/manual/html_node/CPU-Affinity.html
http://www.kernel.org/doc/man-pages/online/pages/man2/sched_setaffinity.2.html

转载请保留地址:http://www.lenky.info/archives/2012/03/1262http://lenky.info/?p=1262


备注:如无特殊说明,文章内容均出自Lenky个人的真实理解而并非存心妄自揣测来故意愚人耳目。由于个人水平有限,虽力求内容正确无误,但仍然难免出错,请勿见怪,如果可以则请留言告之,并欢迎来讨论。另外值得说明的是,Lenky的部分文章以及部分内容参考借鉴了网络上各位网友的热心分享,特别是一些带有完全参考的文章,其后附带的链接内容也许更直接、更丰富,而我只是做了一下归纳&转述,在此也一并表示感谢。关于本站的所有技术文章,欢迎转载,但请遵从CC创作共享协议,而一些私人性质较强的心情随笔,建议不要转载。

法律:根据最新颁布的《信息网络传播权保护条例》,如果您认为本文章的任何内容侵犯了您的权利,请以Email或书面等方式告知,本站将及时删除相关内容或链接。

  1. 恍惚
    2015年7月8日15:55 | #1

    你好,请教下,cpu亲和性设置对于虚拟机中运行的程序有没有意义?谢谢!

  2. StefanLee
    2014年2月25日09:31 | #2

    博主好,,看了你的文章,很受启发,我有疑问,就是这里的多cpu是指多个物理cpu吗?、、不是我们平时所说的一个cpu多核吗?、

    • lenky
      2014年2月25日13:20 | #3

      这里的多cpu是指cat /proc/cpuinfo里所看到的cpu。也就是平常所说的cpu多核。

  1. 本文目前尚无任何 trackbacks 和 pingbacks.