一、什么是线程的亲和性:
了解 Linux2.6 调度器如何处理 CPU 亲和性(affinity)可以帮助您更好地设计用户空间的应用程序。软亲和性 意味着进程并不会在处理器之间频繁迁移,而 硬亲和性 则意味着进程需要在您指定的处理器上运行。
简单地说,CPU 亲和性(affinity) 就是进程要在某个给定的 CPU 上尽量长时间地运行而不被迁移到其他处理器的倾向性。Linux 内核进程调度器天生就具有被称为 软 CPU 亲和性(affinity) 的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。
2.6 版本的 Linux 内核还包含了一种机制,它让开发人员可以编程实现 硬 CPU 亲和性(affinity)。这意味着应用程序可以显式地指定进程在哪个(或哪些)处理器上运行。
在 Linux 内核中,所有的进程都有一个相关的数据结构,称为 task_struct。这个结构非常重要,原因有很多;其中与 亲和性(affinity)相关度最高的是 cpus_allowed 位掩码。这个位掩码由 n 位组成,与系统中的 n 个逻辑处理器一一对应。 具有 4 个物理 CPU 的系统可以有 4 位。如果这些 CPU 都启用了超线程,那么这个系统就有一个 8 位的位掩码。
如果为给定的进程设置了给定的位,那么这个进程就可以在相关的 CPU 上运行。因此,如果一个进程可以在任何 CPU 上运行,并且能够根据需要在处理器之间进行迁移,那么位掩码就全是 1。实际上,这就是 Linux 中进程的缺省状态。
Linux 内核 API 提供了一些方法,让用户可以修改位掩码或查看当前的位掩码:
二、为什么应该使用硬亲和性(affinity)?
通常 Linux 内核都可以很好地对进程进行调度,在应该运行的地方运行进程(这就是说,在可用的处理器上运行并获得很好的整体性能)。内核包含了一些用来检测 CPU 之间任务负载迁移的算法,可以启用进程迁移来降低繁忙的处理器的压力。
一般情况下,在应用程序中只需使用缺省的调度器行为。然而,您可能会希望修改这些缺省行为以实现性能的优化。让我们来看一下使用硬亲和性(affinity) 的 3 个原因。
原因 1. 有大量计算要做
基于大量计算的情形通常出现在科学和理论计算中,但是通用领域的计算也可能出现这种情况。一个常见的标志是您发现自己的应用程序要在多处理器的机器上花费大量的计算时间。
原因 2. 您在测试复杂的应用程序
测试复杂软件是我们对内核的亲和性(affinity)技术感兴趣的另外一个原因。考虑一个需要进行线性可伸缩性测试的应用程序。有些产品声明可以在 使用更多硬件 时执行得更好。
我们不用购买多台机器(为每种处理器配置都购买一台机器),而是可以:
- 购买一台多处理器的机器
- 不断增加分配的处理器
- 测量每秒的事务数
- 评估结果的可伸缩性
如果应用程序随着 CPU 的增加可以线性地伸缩,那么每秒事务数和 CPU 个数之间应该会是线性的关系(例如斜线图 —— 请参阅下一节的内容)。这样建模可以确定应用程序是否可以有效地使用底层硬件。
Amdahl 法则
Amdahl 法则是有关使用并行处理器来解决问题相对于只使用一个串行处理器来解决问题的加速比的法则。加速比(Speedup) 等于串行执行(只使用一个处理器)的时间除以程序并行执行(使用多个处理器)的时间:
其中 T(j) 是在使用 j 个处理器执行程序时所花费的时间。 |
|
Amdahl 法则说明这种加速比在现实中可能并不会发生,但是可以非常接近于该值。对于通常情况来说,我们可以推论出每个程序都有一些串行的组件。随着问题集不断变大,串行组件最终会在优化解决方案时间方面达到一个上限。
Amdahl 法则在希望保持高 CPU 缓存命中率时尤其重要。如果一个给定的进程迁移到其他地方去了,那么它就失去了利用 CPU 缓存的优势。实际上,如果正在使用的 CPU 需要为自己缓存一些特殊的数据,那么所有其他 CPU 都会使这些数据在自己的缓存中失效。
因此,如果有多个线程都需要相同的数据,那么将这些线程绑定到一个特定的 CPU 上是非常有意义的,这样就确保它们可以访问相同的缓存数据(或者至少可以提高缓存的命中率)。否则,这些线程可能会在不同的 CPU 上执行,这样会频繁地使其他缓存项失效。
原因 3. 您正在运行时间敏感的、决定性的进程
我们对 CPU 亲和性(affinity)感兴趣的最后一个原因是实时(对时间敏感的)进程。例如,您可能会希望使用硬亲和性(affinity)来指定一个 8 路主机上的某个处理器,而同时允许其他 7 个处理器处理所有普通的系统调度。这种做法确保长时间运行、对时间敏感的应用程序可以得到运行,同时可以允许其他应用程序独占其余的计算资源。
三、编程实现硬亲和性:
glibc版本:2.5.12
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int GetCpuCount()
{
return (int)sysconf(_SC_NPROCESSORS_ONLN);
}
void *thread_fun()
{
int i;
while(1)
{
i = 0;
}
return NULL;
}
int main()
{
int cpu_num = 0;
cpu_num = GetCpuCount();
printf("The number of cpu is %d\n", cpu_num);
pthread_t t1;
pthread_t t2;
pthread_attr_t attr1;
pthread_attr_t attr2;
pthread_attr_init(&attr1);
pthread_attr_init(&attr2);
cpu_set_t cpu_info;
__CPU_ZERO(&cpu_info);
__CPU_SET(0, &cpu_info);
if (0!=pthread_attr_setaffinity_np(&attr1, sizeof(cpu_set_t), &cpu_info))
{
printf("set affinity failed");
return;
}
__CPU_ZERO(&cpu_info);
__CPU_SET(1, &cpu_info);
if (0!=pthread_attr_setaffinity_np(&attr2, sizeof(cpu_set_t), &cpu_info))
{
printf("set affinity failed");
}
if (0!=pthread_create(&t1, &attr1, thread_fun, NULL))
{
printf("create thread 1 error\n");
return;
}
if (0!=pthread_create(&t2, &attr2, thread_fun, NULL))
{
printf("create thread 2 error\n");
return;
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
}
备:同样也可以使用函数pthread_setaffinity_np函数来替代pthread_attr_setaffinity_np,即:
将
if (0!=pthread_attr_setaffinity_np(&attr1, sizeof(cpu_set_t), &cpu_info))
替换为;
if (0!=pthread_setaffinity_np(t1, sizeof(cpu_set_t), &cpu_info))
同时,pthread_setaffinity_np需放在pthread_create后面;
详细代码如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int GetCpuCount()
{
return (int)sysconf(_SC_NPROCESSORS_ONLN);
}
void *thread_fun()
{
int i;
while(1)
{
i = 0;
}
return NULL;
}
int main()
{
int cpu_num = 0;
cpu_num = GetCpuCount();
printf("The number of cpu is %d\n", cpu_num);
pthread_t t1;
pthread_t t2;
pthread_attr_t attr1;
pthread_attr_t attr2;
pthread_attr_init(&attr1);
pthread_attr_init(&attr2);
if (0!=pthread_create(&t1, &attr1, thread_fun, NULL))
{
printf("create thread 1 error\n");
return;
}
if (0!=pthread_create(&t2, &attr2, thread_fun, NULL))
{
printf("create thread 2 error\n");
return;
}
cpu_set_t cpu_info;
__CPU_ZERO(&cpu_info);
__CPU_SET(0, &cpu_info);
if (0!=pthread_setaffinity_np(t1, sizeof(cpu_set_t), &cpu_info))
{
printf("set affinity failed");
}
__CPU_ZERO(&cpu_info);
__CPU_SET(1, &cpu_info);
if (0!=pthread_setaffinity_np(t2, sizeof(cpu_set_t), &cpu_info))
{
printf("set affinity failed");
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
}