OpenMP是作为共享存储标准而问世的。它是为在多处理机上编写并行程序而设计的一个应用
编程接口。它包括一套编译指导语句和一个用来支持它的函数库。
目前双核心的 CPU 当道,AMD 的 Athlon64x2、Intel 的 Pentium-D、Core Duo,以及
即将上市的 Core 2 Duo,俨然将成为下一代电脑的主流(尤其是超低价的 Pentium D,绝
对是现阶段 C/P 值极高的双核心 CPU)。但是双核心有什麼用呢?
对於一般单一执行绪(single thread)的程式,多核心的处理器并没有办法提升它的
处理效能;不过对於多执行绪(multi thread)的程式,就可以透过不同的核心同时计算,
来达到加速的目的了!简单的例子,以单执行绪的程式来说,一件事做一次要十秒的话,要
做十次,都丢给同一颗核心做的话,自然就是 10 秒 * 10 次,也就是 100 秒了;但是以
多执行绪的程式来说,它可以把这一件事,分给两颗核心各自做,每颗核心各做 5 次,所
以所需要的时间就只需要 50 秒!
当然,多执行绪的程式实际上没这麼简单。在工作的切割、结合上,也是要多花时间的
,所以在现实中,即使最佳状况,双核心的效能也不会是 1 + 1 = 2 这样的理想化。除此
之外,也不是所有工作都是可以切割的!很多工作是有关联性的,这样如果直接切割给不同
的处理核心各自去平行运算,出来的结果是肯定有问题的。而且,多执行绪的程式在编写、
维护上,也都比单一执行绪的程式复杂上不少。
不过,如果电脑本身是多处理器、多核心处理器,或是处理器拥有像 Intel Hyper-
Threading Technology 这类的能在同一个时间处理多个执行绪的功能的话,那把各自独立
的工作由单一执行绪改成多执行绪,在执行的效率上,大多还是会有增进的!
---------------------------------------------------------------------------
-----
[编辑本段]多执行绪的程式
写程式的时候该怎麼去写多执行绪的程式呢?一般的方法,就是真的利用 thread 的控
制,去实际在程式中去产生其他的 thread 来处理。像 POSIX Threads 这套 library,就
是用来产生、控制执行绪的函式库。而像 Microsoft VisualStudio 2005 中,也有提供控
制 thread 的功能。这种方法,大多就是产生多个 thread,而再由主要的 thread 把工作
拆开,分给各 thread 去运算,最後再由主要的 thread 回收结果、整合。
但是,实际上要去控制 thread 是满麻烦的~在程式的编写上,也会复杂不少;而如果
我们只是想要把一些简单的回圈平行化处理,用 thread library 来控制,实在有点杀鸡用
牛刀的感觉。这时候,用 Open MP 就简单多了!OpenMP 是一种能透过高阶指令,很简单地
将程式平行化、多执行绪化的 API;在最简单的情形,甚至可以只加一行指令,就可以将回
圈内的程式平行化处理了!
---------------------------------------------------------------------------
-----
[编辑本段]OpenMP 的基本使用
要在 Visual C++ 2005 中使用 openMP 其实不难,只要将 Project 的 Properties 中
C/C++ 里 Language 的 OpenMP Support 开启(参数为 /openmp),就可以让 VC++2005 在
编译时支援 OpenMP 的语法了;而在使用到 OpenMP 的档案,则需要先 include OpenMP 的
header file : omp.h。
而要将 for 回圈平行化处理,该怎麼做呢?非常简单,只要在前面加上一行
#pragma omp parallel for
就够了!
也可以实际用一段简单的程式,来弄清楚它的运作方式。
#include
#include
void Test(int n) {
for (int i = 0; i < 10000; ++i)
{
//do nothing, just waste time
}
printf("%d, ", n);
}
int main(int argc, char* argv[])
{
for (int i = 0; i < 10; ++i)
Test(i);
system("pause");
}
上面的程式,在 main() 是一个很简单的回圈,跑十次,每次都会呼叫 Test() 这个函
氏,并把是回圈的执行次数(i)传进 Test() 并列印出来。想当然耳,它的结果会是:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,而如果想利用 OpenMP 把 mian() 里面的回圈平行化
处理呢?只需要修改成下面的样子:
#include
#include
#include
void Test (int n) {
for (int i = 0; i < 10000; ++i) {
//do nothing, just waste time
}
printf("%d, ", n);
}
int main(int argc, char* argv[]) {
#pragma omp parallel for
for (int i = 0; i < 10; ++i)
Test( i );
system("pause");
}
够简单吧?重头到尾,只加了两行(红色部分)!而执行後,可以发现结果也变了!
0, 5, 1, 6, 2, 7, 3, 8, 4, 9,可以从结果很明显的发现,他没有照著 0 到 9 的顺
序跑了!而上面的顺序怎麼来的?其实很简单,OpenMP 只是把回圈 0 - 9 共十个步骤,拆
成 0 - 4, 5 - 9 两部份,丢给不同的执行绪去跑,所以数字才会出现这样交错性的输出~
而要怎麼确定真的有跑多执行绪呢?如果本来有多处理器、多核心处理器或有 Hyper
Thread 的话,一个单执行绪程式,最多只会把一颗核心的使用量吃完;像比如说在
Pentium 4 HT 上跑,单一执行绪的程式,在工作管理员中看到的 CPU 使用率最多就是 50%
。而利用 OpenMP 把回圈进行平行化处理後,就可以在执行廻圈时,把两颗核心的 CPU 都
榨光了!也就是工作管理员可以看到 CPU 使用率是 100%。
以上内容 来自百度百科
PU Affinity (CPU亲合力)
CPU亲合力就是指在Linux系统中能够将一个或多个进程绑定到一个或多个处理器上运行.
一个进程的CPU亲合力掩码决定了该进程将在哪个或哪几个CPU上运行.在一个多处理器系统
中,设置CPU亲合力的掩码可能会获得更好的性能.
一个CPU的亲合力掩码用一个cpu_set_t结构体来表示一个CPU集合,下面的几个宏分别对这个
掩码集进行操作:
CPU_ZERO() 清空一个集合
CPU_SET()与CPU_CLR()分别对将一个给定的CPU号加到一个集合或者从一个集合中去掉.
CPU_ISSET()检查一个CPU号是否在这个集合中.
其实这几个的用法与select()函数那几个调用差不多.
下面两个函数就是最主要的了:
sched_setaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask)
该函数设置进程为pid的这个进程,让它运行在mask所设定的CPU上.如果pid的值为0,则表示
指定的是当前进程,使当前进程运行在mask所设定的那些CPU上.第二个参数cpusetsize是
mask所指定的数的长度.通常设定为sizeof(cpu_set_t).如果当前pid所指定的CPU此时没有
运行在mask所指定的任意一个CPU上,则该指定的进程会从其它CPU上迁移到mask的指定的
一个CPU上运行.
sched_getaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask)
该函数获得pid所指示的进程的CPU位掩码,并将该掩码返回到mask所指向的结构中.即获得指
定pid当前可以运行在哪些CPU上.同样,如果pid的值为0.也表示的是当前进程.
这几个宏与函数的具体用法前面已经有讲解.
关于cpu_set_t的定义
[Copy to clipboard] [ - ]CODE:
# define __CPU_SETSIZE 1024
# define __NCPUBITS (8 * sizeof (__cpu_mask))
typedef unsigned long int __cpu_mask;
# define __CPUELT(cpu) ((cpu) / __NCPUBITS)
# define __CPUMASK(cpu) ((__cpu_mask) 1 << ((cpu) % __NCPUBITS))
typedef struct
{
__cpu_mask __bits[__CPU_SETSIZE / __NCPUBITS];
} cpu_set_t;
# 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)
在我的机器上sizeof(cpu_set_t)的大小为128,即一共有1024位.第一位代表一个CPU号.某一
位为1则表示某进程可以运行在该位所代表的cpu上.例如
CPU_SET(1, &mask);
则mask所对应的第2位被设置为1.
此时如果printf("%d/n", mask.__bits[0]);就打印出2.表示第2位被置为1了.
具体我是参考man sched_setaffinity文档中的函数的.
然后再参考了一下IBM的 developerWorks上的一个讲解.
http://www.ibm.com/developerworks/cn/linux/l-affinity.html
现在多CPU的趋势越来越大了. 有时候为了更好地操作机器, 需要将某个进程绑定到具体的
CPU上去. 下面给出了一个进程绑定到具体的CPU上去的一个例子.
cpu.c
[Copy to clipboard] [ - ]CODE:
#include
#include
#include
#include
#include
#define __USE_GNU
#include
#include
#include
int main(int argc, char* argv[])
{
int num = sysconf(_SC_NPROCESSORS_CONF);
int created_thread = 0;
int myid;
int i;
int j = 0;
cpu_set_t mask;
cpu_set_t get;
if (argc != 2)
{
printf("usage : ./cpu num/n");
exit(1);
}
myid = atoi(argv[1]);
printf("system has %i processor(s). /n", num);
CPU_ZERO(&mask);
CPU_SET(myid, &mask);
if (sched_setaffinity(0, sizeof(mask), &mask) == -1)
{
printf("warning: could not set CPU affinity, continuing.../n");
}
while (1)
{
CPU_ZERO(&get);
if (sched_getaffinity(0, sizeof(get), &get) == -1)
{
printf("warning: cound not get cpu affinity,
continuing.../n");
}
for (i = 0; i < num; i++)
{
if (CPU_ISSET(i, &get))
{
printf("this process %d is running processor :
%d/n",getpid(), i);
}
}
}
return 0;
}
下面是在两个终端分别执行了./cpu 0 ./cpu 2 后得到的结果. 效果比较明显.
QUOTE:
Cpu0 : 5.3%us, 5.3%sy, 0.0%ni, 87.4%id, 0.0%wa, 0.0%hi, 2.0%si, 0.0%st
Cpu1 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu2 : 5.0%us, 12.2%sy, 0.0%ni, 82.8%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu3 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu4 : 0.0%us, 0.0%sy, 0.0%ni, 99.7%id, 0.3%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu5 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu6 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu7 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
那个是在运行top命令之后再按1就出现了对每个CPU的统计.
us 用户空间占用CPU百分比
sy 内核空间占用CPU百分比
ni 用户进程空间内改变过优先级的进程占用CPU百分比
id 空闲CPU百分比
wa 等待输入输出的CPU时间百分比
后面两个我也不是太清楚,麻烦清楚的朋友说一下.
使用#define __USE_GNU是为了使用CPU_SET()等宏. 具体在/usr/include/sched.h中有如下
的定义.
因为我开始编写的时候没有加这个宏, 出现了错误, 后来查看了这个定义才知道要使用这个
东西.
[Copy to clipboard] [ - ]CODE:
#ifdef __USE_GNU
/* Access macros for `cpu_set'. */
#define CPU_SETSIZE __CPU_SETSIZE
#define CPU_SET(cpu, cpusetp) __CPU_SET (cpu, cpusetp)
#define CPU_CLR(cpu, cpusetp) __CPU_CLR (cpu, cpusetp)
#define CPU_ISSET(cpu, cpusetp) __CPU_ISSET (cpu, cpusetp)
#define CPU_ZERO(cpusetp) __CPU_ZERO (cpusetp)
/* Set the CPU affinity for a task */
extern int sched_setaffinity (__pid_t __pid, size_t __cpusetsize,
__const cpu_set_t *__cpuset) __THROW;
/* Get the CPU affinity for a task */
extern int sched_getaffinity (__pid_t __pid, size_t __cpusetsize,
cpu_set_t *__cpuset) __THROW;
#endif
以上转自http://linux.chinaunix.net/bbs/viewthread.php?tid=904906&extra=page%3D1%26amp%3Bfilter%3Ddigest