openMP编程探索2——循环并行化


   openMP并不是只能对循环来并行的,循环并行化单独拿出来说是因为它在科学计算中非常有用,比如向量、矩阵的计算。所以我单独拿出这一部分给大家讲讲。这里主要讲解的是for循环。

编译指导语句:

    一般格式:

    #pragma omp parallel for [clause[clause…]]

    for(index = first; qualification; index_expr)

    {…}

    第一句中[]的部分是可选的,由自己的程序并行特点而定。大家先不要把精力放到这里面。后面的文章中会继续讲解的。

并行化for的编写规则

    1、index的值必须是整数,一个简单的for形式:for(int i = start; i < end; i++){…} 。

    2、start和end可以是任意的数值表达式,但是它在并行化的执行过程中值不能改变,也就是说在for并行化执行之前,编译器必须事先知道你的程序执行多少次,因为编译器要把这些计算分配到不同的线程中执行。

    3、循环语句只能是单入口但出口的。这里只要你避免使用跳转语句就行了。具体说就是不能使用goto、break、return。但是可以使用continue,因为它并不会减少循环次数。另外exit语句也是可以用的,因为它的能力太大,他一来,程序就结束了。

例子讲解

例1、for循环并行

#include 
#include "omp.h" 
int main(int argc, char* argv[]) 

    int i; 
    #pragma omp parallel for 
    for (i = 0; i < 12; i++) 
    {        printf("i = %d  %d/n", i, omp_get_thread_num());    } 
    return 0; 
}

例1的执行结果如图1所示:

image

图1、例1的执行结果

从结果中可以看出 i 属于{0,1,2}时由0号线程执行,i 属于{3,4,5}时由1号线程执行,i 属于{6,7,8}时由2号线程执行,i 属于{9,10,11}时由3号线程执行。omp_get_thread_num()这个函数通过执行结果大家也知道了,他返回每个线程的编号。

并行编译子句

    openMP中有多种并行化子句,这些子句都是为控制循环并行化编译而设定的,这里我们主要关注数据作用域子句,这里的数据作用域是指各个线程是否对某一变量有权访问。shared子句用来标记变量在各个线程之间是共享的,private子句标记变量在各个线程之间是私有的,实际上它会在在每个线程中保存一个副本。默认情况下,并行执行的变量是共享的。至于其它编译子句将在后面的文章中介绍。

用实例讲解数据作用域子句

实际上我很难想到一个综合的例子来讲解这种子句的限制异同,所以我写了几个例子。

例2、private

#include 
#include "omp.h" 
int main(int argc, char* argv[]) 

    float x = 4.3f; 
    int i; 
    #pragma omp parallel for private(x) 
    for (i = 0; i < 12; i++) 
    { 
        x = 0; 
        printf("parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num()); 
    } 
    printf("/nserial   x = %.1f, thread nummber:%d/n", x, omp_get_thread_num()); 
    return 0; 
}

image

图2、例2执行结果

例3 firstprivate(var):指定var在每个线程中都有一个副本,并且var的初始值在并行执行开始之前定义,每个并行线程的var的副本初值就是串行时定义的初始值。程序结束后串行程序中的var值并不会改变。

#include 
#include "omp.h" 
int main(int argc, char* argv[]) 

    float x = 4.3f; 
    int i; 
    #pragma omp parallel for firstprivate(x) 
    for (i = 0; i < 12; i++) 
    { 
        x += 1.0f; 
        printf("parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());        
    } 
    printf("/nserial   x = %.1f, thread nummber:%d/n", x, omp_get_thread_num()); 
    return 0; 
}

image

图3、例3的执行结果

例4 lastprivate(var):指定最后多线程执行完后原串行循环最后一次var的值带到主线程(串行部分)

#include 
#include "omp.h" 
int main(int argc, char* argv[]) 

    float x = 4.3f; 
    int i; 
    #pragma omp parallel for lastprivate(x) 
    for (i = 0; i < 12; i++) 
    {        
        x = 0.0f; 
        printf("parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());        
    } 
    printf("/nserial   x = %.1f, thread nummber:%d/n", x, omp_get_thread_num()); 
    return 0; 
}

image

图4、例4的执行结果

例5 firstprivate与lastprivate联用,很奇怪openMP很多情况下是不允许某个变量被指定两次规则的,他俩却可以,呵呵,而且配合效果还不错。

#include 
#include "omp.h" 
int main(int argc, char* argv[]) 

    float x = 4.3f; 
    int i; 
    #pragma omp parallel for firstprivate(x) lastprivate(x) 
    for (i = 0; i < 12; i++) 
    {        
        x += (float)omp_get_thread_num(); 
        printf("parallel x = %.1f, thread nummber:%d/n", x, omp_get_thread_num());        
    } 
    printf("/nserial   x = %.1f, thread nummber:%d/n", x, omp_get_thread_num()); 
    return 0; 
}

image

图5、例5的执行结果

    从上面的例2、3的程序中可以看出例2中每个线程中x都是私有的,它属于每个线程,在主线程的定义并不能带入到各个线程中,使用firstprivate后,x在主线程的初始值可以带到各个线程中,在图3可以看出每个线程x的输出结果实际是相同的,但是在并行执行结束后,主线程中的x值仍然为4.3。从例4的执行结果可以看出最后x的值带出到了主线程中,这个x值到底是哪个线程中的哪?答案是最后一句x赋值后的值,哪个线程执行完的最晚就是哪个x的值。例5显示firstprivate与lastprivate联合使用的执行结果。

例6 reduction规约操作,

    执行reduction的变量要特别注意,以reduction(+:sum)为例。

    第一种情况:sum为局部变量。这是你必须为sum在串行程序中赋初值,sum 被设置为每个线程私有,先各自执行完算出各自的sum值,最后主线程会将 《线程数+1》个sum变量规约,比如你num_thread(4),在开始并行执行之前你对规约变量赋初值为10,并行时假设每个线程算的的sum值为1,那么最终sum带到串行程序中的变量值为14(串行的10+四个线程的1)。

    第二种情况:sum为全局变量。这是你不必为sum赋初始值,因为此时默认串行的sum值为0,进入每个线程的sum值也是0,规约时仍然是将《线程数+1》个sum值相加,因为你并没有对全局的sum赋初值,所以最后规约的结果看着像是只有各线程的sum参加了规约操作。其实当你将全局的sum赋初值时,你会发现最后规约的sum值又多加了全局变量sum的串行程序结果。

    重要提醒:不管你怎样设计sum的串行声明形式,只要他在被定义为规约变量,每次进入并行线程的sum值都是0;

    也许你想把每个并行线程的sum值初始化成一个非0的值,然后再各自线程中在使用,那么我可以告诉你,别想了(至少我没有做到)。因为我规约sum值,如果这个规约有意义你的每个线程应该是各自独立未回各自的sum的,那么这个初始值使用0就已经非常好了,因为各自的sum计算如果结果一样,你为何不直接用一句乘法哪(线程数*一个线程计算的sum值)。

#include 
#include "omp.h" 
int main(int argc, char* argv[]) 

    float x = 0.0f; 
    int i; 
    float sum = 0.0f; 
#pragma omp parallel for private(x) reduction(+:sum) 
    for (i = 0; i < 12; i++) 
    {        
        x = (float)omp_get_thread_num(); 
        sum += x; 
        printf("parallel sum = %.1f, thread nummber:%d/n", sum, omp_get_thread_num());        
    } 
    printf("/nserial   sum = %.1f, thread nummber:%d/n", sum, omp_get_thread_num()); 
    return 0; 
}

image

图6、例6执行结果

    在例6中我使用了reduction(+:sum),这表示每个线程对sum这个共享变量执行加操作时其它任何线程不能对它进行加操作,实际上我们这样理解是有偏差的,真正的机理在执行结果中不难看出,实际每个线程都拷贝了一个sum的副本,先在自己执行时加完sum,等所有线程都执行结束后,主线程再将每个线程的sum副本的值加起来返回给主线程中sum。

小结

    本节主要讲述了for语句的并行化。现在为止大家应该可以熟练使用for并行化了。文章中可能还有些不全面的地方,热切期望各位读者能给出批评和指正,期待中……

转载自:http://blog.csdn.net/bendanban/article/details/6303100

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值