OpenMP是一个方便多线程计算的一个标准。现代的编译器例如GCC就自带了OpenMP的实现,但是要开启OpenMP功能,需要在编译的时候加入 -fopenmp 参数。下面是一段Hello Xiaoxia的代码来展示OpenMP的用法。
#include <iostream> using namespace std; int main(){ #pragma omp parallel { cout << "Hello Xiaoxia!\n"; } return 0; }
编译运行,
root@xiaoxia-pc:~/study/openmp# g++ mp.cpp -o mp -fopenmp && ./mp
Hello Xiaoxia!
Hello Xiaoxia!
Hello Xiaoxia!
Hello Xiaoxia!
和正常的Hello代码差不多,不同的地方是多了一句#pragma关于omp的一些声明,以及用花括号封装起来的语句(如果是单个语句,可以不要花括号)。
因为我的CPU是4线程的,所以OpenMP默认使用4线程来执行代码,运行结果看到4个Hello Xiaoxia!
对代码做点小改动,探究一下:
#include <iostream> using namespace std; int main(){ #pragma omp parallel { cout << "Hello Xiaoxia!\n"; sleep(20); } return 0; }
编译运行:
root@xiaoxia-pc:~/study/openmp# g++ mp.cpp -o mp -fopenmp
root@xiaoxia-pc:~/study/openmp# ./mp &
[1] 15433
root@xiaoxia-pc:~/study/openmp# Hello Xiaoxia!
Hello Xiaoxia!
Hello Xiaoxia!
Hello Xiaoxia!
ls /proc/15433/task
15433 15434 15435 15436
由此验证OpenMP的确打开了4个线程……值得注意的是,一旦OpenMP在并行区域里启动了4个线程进行工作之后,并不会马上结束被系统回收,而是留作后面进行并行计算的时候继续使用!!!
使用omp_set_num_threads来改变线程个数!
#include <stdio.h> #include <omp.h> int main(){ omp_set_num_threads(10); #pragma omp parallel { printf("I'm %d!\n", omp_get_thread_num()); } return 0; }
编译运行:
root@xiaoxia-pc:~/study/openmp# g++ mp.cpp -o mp -fopenmp
root@xiaoxia-pc:~/study/openmp# ./mp
I’m 0!
I’m 8!
I’m 1!
I’m 2!
I’m 3!
I’m 5!
I’m 6!
I’m 7!
I’m 4!
I’m 9!
OpenMP的多线程运算可以用在for循环里,可以选择不同的方式分配线程处理循环中的过程,最简单的用法如下:
#include <stdio.h> #include <omp.h> int main(){ #pragma omp parallel for for(int i=0; i<10; i++) printf("%d have %d\n", omp_get_thread_num(), i); return 0; }
编译运行,
root@xiaoxia-pc:~/study/openmp# g++ mp.cpp -o mp -fopenmp
root@xiaoxia-pc:~/study/openmp# ./mp
2 has 6
2 has 7
2 has 8
1 has 3
1 has 4
1 has 5
0 has 0
0 has 1
0 has 2
3 has 9
OK!OpenMP还有很多用法,我觉得用的比较通常的是,
#pragma omp single
只能由一个线程执行下一行代码,如果没有nowait参数,则其他线程运行到这里会等待。
#pragma omp master
跟single差不多,但是只能由主线程执行下一行代码,如果没有nowait参数,则其他线程运行到这里会等待。
#pragma omp for
指一个for循环里需要指派不同的线程执行不同的iteration(不知道中文怎么表达)。
#pragma omp critical
指下一行代码同时只能由一个线程执行(注意跟single的区别)。
OK!暂且了解这么多够了。先实践一下!
我在想除了计算PI,还有啥运算比较复杂的东西呢?像微积分、FFT那种太深奥的我又不是很会。最后想了一下还是用回文数吧,因为我高中用的手机号码是一个11位的回文数字,哈哈!但上大学之后用广州卡了 🙂
给出一个整数,如何快速判断是否为一个回文数字?没有想多远,留给各位读者思考了。我直接一个数字一个数字的比较,代码如下,
/* Any faster methods ? */ bool is_reversible(unsigned int n) { int digits[16], m=n, i=0, j; do{ digits[i++] = m % 10; m /= 10; }while(m > 0); j = i >> 1; while(n>0 && i>j){ if(digits[--i] != n % 10) return false; n /= 10; } return true; }
使用并行查找一个随机生成的整数数组data里面的回文数,
void parallel_find() { int rev_data = 0; #pragma omp parallel shared(rev_data) { #pragma omp for for(int i=0; i<NUM_DATA; i++) if(is_reversible(data[i])) #pragma omp critical rev_data ++; #pragma omp master printf("rev: %u\n", rev_data); } }
先用parallel定义要并行运算的区域,程序运行到这里,便会创建一定数量的线程执行该区域的代码。
关键字for把i取值的不同区间分配给不同的线程来执行运算。
因为rev_data是多线程共享的变量,多线程写入操作的时候,使用了critical关键字来避免race condition(中文怎么翻译?)。
最后,使用主线程打印结果。
主程序如下:
#include <stdio.h> #include <stdlib.h> #include <omp.h> #include <unistd.h> #include <math.h> #define NUM_DATA 20000000 #define THREAD_COUNT 4 unsigned int data[NUM_DATA]; int main(){ /* Give me more process priority */ nice(-20); srand(0); #ifdef OPENMP omp_set_num_threads(THREAD_COUNT); #endif /* Randomize test data */ printf("Generating random numbers...\n"); for(int i=0; i<NUM_DATA; i++) data[i] = rand(); for(int j=0; j<10; j++) parallel_find(); return 0; }
程序运行生成20000000个随机数,大约占用80MB内存,大约需要0.5秒。
然后使用for循环调用10次并行查找,目的是方便测试并行计算的性能。做实验一般都是多次操作,数据取平均值嘛!
系统:Kubuntu11.04
CPU:Intel(R) Core(TM) i3 CPU M 330 @ 2.13GHz (双核四线程)
内存:2GB
测试结果,
NUM_DATA = 20,000,000
THREAD_COUNT = 1
root@xiaoxia-pc:~/study/openmp# g++ mp.cpp -o mp
root@xiaoxia-pc:~/study/openmp# time ./mp
Generating random numbers…
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140real 0m22.958s
user 0m22.897s
sys 0m0.040s
NUM_DATA = 20,000,000
THREAD_COUNT = 2
root@xiaoxia-pc:~/study/openmp# g++ mp.cpp -o mp -fopenmp -DOPENMP
root@xiaoxia-pc:~/study/openmp# time ./mp
Generating random numbers…
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140real 0m14.061s
user 0m27.542s
sys 0m0.044s
NUM_DATA = 20,000,000
THREAD_COUNT = 4
root@xiaoxia-pc:~/study/openmp# g++ mp.cpp -o mp -fopenmp -DOPENMP
root@xiaoxia-pc:~/study/openmp# time ./mp
Generating random numbers…
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140real 0m12.997s
user 0m49.575s
sys 0m0.080s
NUM_DATA = 20,000,000
THREAD_COUNT = 8
root@xiaoxia-pc:~/study/openmp# g++ mp.cpp -o mp -fopenmp -DOPENMP
root@xiaoxia-pc:~/study/openmp# time ./mp
Generating random numbers…
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140real 0m13.504s
user 0m49.383s
sys 0m0.100s
NUM_DATA = 20,000,000
THREAD_COUNT = 16
root@xiaoxia-pc:~/study/openmp# g++ mp.cpp -o mp -fopenmp -DOPENMP
root@xiaoxia-pc:~/study/openmp# time ./mp
Generating random numbers…
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140
rev: 1140real 0m14.062s
user 0m53.675s
sys 0m0.128s
观察不使用OpenMP的单线程计算与使用OpenMP的不同线程数目的并行计算。
在启用OpenMP使用双线程后,消耗时间减少了将近一半。在四个线程的时候,CPU功耗最大,运算速度达到最快,消耗时间最小。
观察上图,有点奇怪,我在想,Linux内核对user time的计算,是以CPU为单位还是以CPU线程为单位???如果是以CPU线程为单位,为何在两个线程的时候,user time没有翻倍呢?
内核态CPU在增长是显然的,因为线程多了,在内核态下调度的时间就多了 🙂
最后,使用OpenMP重写一下之前的一个多线程QuickSort快排。
详见,
我一开始在QuickSort递归函数里使用omp sections,结果速度慢了好10多倍!!!
后来发现是因为每次到达sections区域的时候,程序都会等待所有线程就绪,才会执行区域内的代码,这样,多线程不比单线程慢才怪!
下面介绍task关键字,在非递归或者递归函数里都很有用,因为它完美支持嵌套使用。我的理解是,task是用来调度空闲的线程来处理下一行代码的,如果当前没有空闲的线程,会等到其他线程空闲的时候再处理。
#include <stdio.h> #include <stdlib.h> #include <omp.h> #include <unistd.h> #define MAX_DATA 20000000 #define THREAD_COUNT 16 unsigned int data[MAX_DATA]; void QuickSort(int begin, int end) { int middle, i=begin, j=end; middle = data[(begin + end) >> 1]; /* Iterating */ do{ while(data[i] < middle) i++; while(data[j] > middle) j--; if(i <= j){ register unsigned int t = data[i]; data[i++] = data[j]; data[j--] = t; } }while(i < j); /* Divide & Conquer */ if(begin < j) #pragma omp task QuickSort(begin, j); if(i < end) #pragma omp task QuickSort(i, end); } int main(){ nice(-20); omp_set_num_threads(THREAD_COUNT); /* Randomize test data */ printf("Generating random numbers...\n"); for(int i=0; i<MAX_DATA; i++) data[i] = rand(); printf("Quick sorting\n"); #pragma omp parallel { #pragma omp single nowait QuickSort(0, MAX_DATA-1); } return 0; }
测试数据,
MAX_DATA = 20,000,000
THREAD_COUNT = 1, 2, 4, 8, 16
直接上图表,
这个结果让我对OpenMP还是有点失望了,因为我发现无法达到我之前用pthread进行多线程运算的水平。可以到这里看看!
也许是openmp封装了不少东西吧!!!
沙发
被你抢到了。。。
xiaoxia,用的什么制图工具,这些图表很好看
同问
第一张图片是来自wiki的!
后面分析数据的图片,是用LibreOffice的电子表格生成。
openmp的优势是在于不用显式管理线程吗?
嗯,openmp是一个编译器相关的标准来的,很多编译器都支持。
突然感觉自己看代码是那么的无力,我到底是什么了。
昏昏欲睡,又梦醒时分,我想起我曾看过这篇文章,似乎还是小虾大神写的。
我又想起,我今天曾去过医院。
小虾大神,请原谅我没有仔细的看你这篇文章,因为我实在是太累了。
我担心自己会在一瞬间死去,生命是如此的脆弱,我又能如何。
兄弟!要我打120不?
兄弟,你是好人,但生死由天安排,我只能让自己在回忆中死去。
我的博文结构基本上都这样,文章里有代码,代码里有文章。当然似曾相识啦!
生命是如此顽强!
请问 #pragma这个东西在GCC里能用么?
可以的!Have a try… 🙂
我用mingw32试过了!不成功,我还以为是VC的产物呢!
虾哥,有研究过如何打开QQ聊天记录的Msg2.0.db文件么?
没有研究过 🙂
OpenMP还有一些细节选项可以调一调的。不过总体来说局限比较大,而且基本只能在一台机器上用,比起开线程也就编程方便点而已。并行计算库还是如MPI这样的意义要大一点。
race condition可以翻译成竞争吧(赛跑)?