nginx中CPU亲和性源码解读

1.   CPU亲和性

1.1   CPU亲和性介绍

简单地说,CPU 亲和性(affinity)就是进程要在某个给定的 CPU 上尽量长时间地运行而不被迁移到其他处理器的倾向性。Linux 内核进程调度器天生就具有被称为软 CPU 亲和性(affinity)的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。

2.6 版本的 Linux 内核还包含了一种机制,它让开发人员可以编程实现硬 CPU 亲和性(affinity)。这意味着应用程序可以显式地指定进程在哪个(或哪些)处理器上运行。

1.2为什么要使用CPU亲和性

通常 Linux 内核都可以很好地对进程进行调度,在应该运行的地方运行进程(这就是说,在可用的处理器上运行并获得很好的整体性能)。内核包含了一些用来检测 CPU 之间任务负载迁移的算法,可以启用进程迁移来降低繁忙的处理器的压力。

一般情况下,在应用程序中只需使用缺省的调度器行为。然而,您可能会希望修改这些缺省行为以实现性能的优化。让我们来看一下使用硬亲和性(affinity) 的 3 个原因。

原因 1. 有大量计算要做

基于大量计算的情形通常出现在科学和理论计算中,但是通用领域的计算也可能出现这种情况。一个常见的标志是您发现自己的应用程序要在多处理器的机器上花费大量的计算时间。

原因 2. 您在测试复杂的应用程序

测试复杂软件是我们对内核的亲和性(affinity)技术感兴趣的另外一个原因。考虑一个需要进行线性可伸缩性测试的应用程序。有些产品声明可以在 使用更多硬件 时执行得更好。

我们不用购买多台机器(为每种处理器配置都购买一台机器),而是可以:

购买一台多处理器的机器

不断增加分配的处理器

测量每秒的事务数

评估结果的可伸缩性

如果应用程序随着 CPU 的增加可以线性地伸缩,那么每秒事务数和 CPU 个数之间应该会是线性的关系。

图1.1 Amdahl法则

 

Amdahl 法则说明这种加速比在现实中可能并不会发生,但是可以非常接近于该值。对于通常情况来说,我们可以推论出每个程序都有一些串行的组件。随着问题集不断变大,串行组件最终会在优化解决方案时间方面达到一个上限。

Amdahl 法则在希望保持高 CPU 缓存命中率时尤其重要。如果一个给定的进程迁移到其他地方去了,那么它就失去了利用 CPU 缓存的优势。实际上,如果正在使用的 CPU 需要为自己缓存一些特殊的数据,那么所有其他 CPU 都会使这些数据在自己的缓存中失效。

因此,如果有多个线程都需要相同的数据,那么将这些线程绑定到一个特定的 CPU 上是非常有意义的,这样就确保它们可以访问相同的缓存数据(或者至少可以提高缓存的命中率)。否则,这些线程可能会在不同的 CPU 上执行,这样会频繁地使其他缓存项失效。

原因 3. 您正在运行时间敏感的、决定性的进程

我们对 CPU 亲和性(affinity)感兴趣的最后一个原因是实时(对时间敏感的)进程。例如,您可能会希望使用硬亲和性(affinity)来指定一个 8 路主机上的某个处理器,而同时允许其他 7 个处理器处理所有普通的系统调度。这种做法确保长时间运行、对时间敏感的应用程序可以得到运行,同时可以允许其他应用程序独占其余的计算资源。

下面的样例应用程序显示了这是如何工作的。

1.3 Linux 关于CPU亲和性API介绍

Linux中针对cpu亲和性特性提供的API如表1.1所示,表中cpu_set_t是一个掩码数组,一共有1024位,每一位对应系统中的一个逻辑处理器,最低位对应系统中的第一个逻辑处理器,而最高位则对应系统中最后一个逻辑处理器。如在一个四核的服务器上,0001表示第一个第一个逻辑处理器,0010表示第二个逻辑处理器,以此类推,在实际编程过程中不应该直接修改位掩码,而是使用系统提供的操作宏。

 

表1.1 linux提供的CPU亲和性API或或定义原型

API原型或宏定义

功能描述

参数意义

int sched_setaffinity(pid_t pid, size_t cpusetsize,cpu_set_t *mask)

设定pid绑定的CPU

pid:设定的进程的进程号

cpusetsizesizeof(cpu_set_t)

mask:设置的cpu掩码

int sched_getaffinity(pid_t pid, size_t cpusetsize,cpu_set_t *mask)

查看pid绑定的CPU

同上

void CPU_ZERO(cpu_set_t *set)

CPUset进行初始化,将其设置为空集

 

void CPU_SET(int cpu, cpu_set_t *set)

cpu加入CPUset

cpucpu编号,从0开始到cpu_num.

void CPU_CLR(int cpu, cpu_set_t *set)

cpuCPUset中清除

 

int CPU_ISSET(int cpu, cpu_set_t *set)

判断cpu是否在CPUset中,若在,返回true否则返回false

 

需要注意的是,当进程设置了CPU亲和性后,进程就被绑定了,只能在那些对应的位被设置的逻辑处理器上运行,如果进程没有显示对CPU亲和性进行设置,则默认所有的位均被置位。另外,CPU亲和性具有遗传性,即设置了CPU亲和性的进程会将这些CPU亲和性传递给从他们派生的子进程,当然,子进程可以调用系统提供的接口,重新对CPU亲和性进行设置。

2Nginx中的CPU亲和性

Nginx作为一款高性能的web服务器,根据实际使用系统的支持情况提供了CPU亲和性的能力。在实际的使用中,如果要使用nginxCPU亲和性的功能,需要进行一定的配置。

2.1 Nginx CPU亲和性配置

Nginx中配置CPU亲和性的语法如图2.1所示。

2.1 nginxCPU亲和性配置的语法

配置示例:

worker_processes     4;

worker_cpu_affinity 0001 0010 0100 1000;

此配置的含义为共启动4worker进程,第一个进程绑定到系统的第一个逻辑处理器,第二个进程绑定到系统的第二个逻辑处理器,以此类推。另外,也可以将一个进程绑定到多个CPU, 示例如下:

worker_processes     2;

worker_cpu_affinity 0101 1010;

此示例表示启动两个worker进程,其中第一个进程绑定到cpu0/cpu2,第二个进程绑定到cpu1/cpu3

2.2 Nginx CPU亲和性代码简析

2.2.1 cpu亲和性配置读取

static ngx_command_t  ngx_core_commands[] = {

……

    { ngx_string("worker_cpu_affinity"),

      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_1MORE,

      ngx_set_cpu_affinity,

      0,

      0,

      NULL },

……

}

CPU亲和性的配置是在core模块初始化过程中读取的,从core_module的命令配置可见,读取CPU亲和性相关的回调函数为ngx_set_cpu_affinity,其实现位于nginx.c中。

static char *

ngx_set_cpu_affinity(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)

{

#if (NGX_HAVE_CPU_AFFINITY)

    ngx_core_conf_t  *ccf = conf;

    u_char            ch;

    uint64_t         *mask;

    ngx_str_t        *value;

    ngx_uint_t        i, n;

    if (ccf->cpu_affinity) {

        return "is duplicate";

    }

    mask = ngx_palloc(cf->pool, (cf->args->nelts - 1) * sizeof(uint64_t));

    if (mask == NULL) {

        return NGX_CONF_ERROR;

    }

    ccf->cpu_affinity_n = cf->args->nelts - 1;

    ccf->cpu_affinity = mask;

    value = cf->args->elts;

    for (n = 1; n < cf->args->nelts; n++) {

        if (value[n].len > 64) {

            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,

                         "\"worker_cpu_affinity\" supports up to 64 CPUs only");

            return NGX_CONF_ERROR;

        }

        mask[n - 1] = 0;

        for (i = 0; i < value[n].len; i++) {

            ch = value[n].data[i];

            if (ch == ' ') {

                continue;

            }

            mask[n - 1] <<= 1;

            if (ch == '0') {

                continue;

            }

            if (ch == '1') {

                mask[n - 1] |= 1;

                continue;

            }

            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,

                          "invalid character \"%c\" in \"worker_cpu_affinity\"",

                          ch);

            return NGX_CONF_ERROR;

        }

    }

#else

    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,

                       "\"worker_cpu_affinity\" is not supported "

                       "on this platform, ignored");

#endif

    return NGX_CONF_OK;

}

此函数功能简单易懂,主要实现将配置文件转换成uint64_t位域的格式,并将转换后的数组保存到ccf->cpu_affinity中,同时通过ccf->cpu_affinity_n记录配置的CPU亲和性参数个数。从代码可以看出,只有定义了NGX_HAVE_CPU_AFFINITY宏后,nginx才可以使用CPU亲和性的特性,NGX_HAVE_CPU_AFFINITY依赖于configure对系统的检测,目前只有linux系统支持CPU亲和性的配置,在http://wiki.nginx.org/NginxHttpMainModule#worker_-

cpu_affInity讨论Nginx性能优化CPU参数worker_cpu_affinity使用说明中明确指出,CPU亲和性的配置是“linux only”的。

2.2.2 cpu亲和性设置

nginx_process_cycle.c中,定义了和CPU亲和性相关的变量cpu_affinity,如下所示:

uint64_t       cpu_affinity;

在调用ngx_start_worker_processes函数创建进程时,有如下代码:

static void

ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)

{

……

    for (i = 0; i < n; i++) {

        cpu_affinity = ngx_get_cpu_affinity(i);

ngx_spawn_process(cycle, ngx_worker_process_cycle, NULL,

                          "worker process", type);

        ……

}

}

进程创建后执行ngx_worker_process_cycle函数,在此函数中,我们可以看到如下代码:

static void

ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)

{

         ……

    ngx_worker_process_init(cycle, 1);

     ……

}

真正的CPU亲和性的设置将在ngx_worker_process_init函数中进行。

static void

ngx_worker_process_init(ngx_cycle_t *cycle, ngx_uint_t priority)

{

         ……

    if (cpu_affinity) {

        ngx_setaffinity(cpu_affinity, cycle->log);

    }

         ……

}

由此可见,如果配置了CPU亲和性,则调用ngx_setaffinity进行真正的CPU亲和性设置,此函数的定义如下:

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

    }

}

函数的功能很简单,从地位到高位遍历cpu_affinity中的位值,如果某位为1,则将对应的CPU设置到CPUmask中,最后调用sched_setaffinity设置本进程的CPU亲和性,同时,由于采用uint64_t存储转换后的结果,nginx最多只支持64 CPUsCPU亲和性的配置。

3.总结

本文对CPU亲和性进行讨论并对nginx中实现CPU亲和性的代码进行了解析。

参考文献

[1] 管理处理器的亲和性(affinity.http://www.ibm.com/developerworks/cn/linux/l-affinity.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值