openmp 订阅
OpenMP是由OpenMP Architecture Review Board牵头提出的,并已被广泛接受,用于共享内存并行系统的多处理器程序设计的一套指导性编译处理方案(Compiler Directive) [1]  。OpenMP支持的编程语言包括C、C++和Fortran;而支持OpenMp的编译器包括Sun Compiler,GNU Compiler和Intel Compiler等。OpenMp提供了对并行算法的高层的抽象描述,程序员通过在源代码中加入专用的pragma来指明自己的意图,由此编译器可以自动将程序进行并行化,并在必要之处加入同步互斥以及通信。当选择忽略这些pragma,或者编译器不支持OpenMp时,程序又可退化为通常的程序(一般为串行),代码仍然可以正常运作,只是不能利用多线程来加速程序执行。 展开全文
OpenMP是由OpenMP Architecture Review Board牵头提出的,并已被广泛接受,用于共享内存并行系统的多处理器程序设计的一套指导性编译处理方案(Compiler Directive) [1]  。OpenMP支持的编程语言包括C、C++和Fortran;而支持OpenMp的编译器包括Sun Compiler,GNU Compiler和Intel Compiler等。OpenMp提供了对并行算法的高层的抽象描述,程序员通过在源代码中加入专用的pragma来指明自己的意图,由此编译器可以自动将程序进行并行化,并在必要之处加入同步互斥以及通信。当选择忽略这些pragma,或者编译器不支持OpenMp时,程序又可退化为通常的程序(一般为串行),代码仍然可以正常运作,只是不能利用多线程来加速程序执行。
信息
支    持
C语言、C++和Fortran
外文名
Open Multi-Processing
类    型
程序设计
提出人
OpenMP Architecture
中文名
共享存储并行编程
openmp简介
OpenMP提供的这种对于并行描述的高层抽象降低了并行编程的难度和复杂度,这样程序员可以把更多的精力投入到并行算法本身,而非其具体实现细节。对基于数据分集的多线程程序设计,OpenMP是一个很好的选择。同时,使用OpenMP也提供了更强的灵活性,可以较容易的适应不同的并行系统配置。线程粒度和负载平衡等是传统多线程程序设计中的难题,但在OpenMP中,OpenMP库从程序员手中接管了部分这两方面的工作。但是,作为高层抽象,OpenMP并不适合需要复杂的线程间同步和互斥的场合。OpenMp的另一个缺点是不能在非共享内存系统(如计算机集群)上使用。在这样的系统上,MPI使用较多。
收起全文
精华内容
参与话题
问答
  • OpenMP+F90并行编程基础

    2010-10-15 16:13:43
    OpenMP is a framework for shared memory parallel computing. OpenMP is a standard C/C++ and Fortran compilers. Compiler directives indicate where parallelism should be used. C/C++ use #pragma ...
  • OpenMP共享内存并行编程详解

    千次阅读 2017-04-02 11:36:39
    实验平台:win7, VS2010   1. 介绍  平行计算机可以简单分为共享内存和分布式内存,共享内存就是多个核心共享一个内存,目前的PC就是这...想要在这些并行计算机上获得较好的性能,进行并行编程是必要条件。目前


     

    实验平台:win7, VS2010

     

    1. 介绍

        平行计算机可以简单分为共享内存和分布式内存,共享内存就是多个核心共享一个内存,目前的PC就是这类(不管是只有一个多核CPU还是可以插多个CPU,它们都有多个核心和一个内存),一般的大型计算机结合分布式内存和共享内存结构,即每个计算节点内是共享内存,节点间是分布式内存。想要在这些并行计算机上获得较好的性能,进行并行编程是必要条件。目前流行的并行程序设计方法是,分布式内存结构上使用MPI,共享内存结构上使用Pthreads或OpenMP。我们这里关注的是共享内存并行计算机,因为编辑这篇文章的机器就属于此类型(普通的台式机)。和Pthreads相比OpenMP更简单,对于关注算法、只要求对线程之间关系进行最基本控制(同步,互斥等)的我们来说,OpenMP再适合不过了。

        本文对windows上Visual Studio开发环境下的OpenMP并行编程进行简单的探讨。本文参考了wikipedia关于OpenMP条目、OpenMP.org(有OpenMP Specification)、MSDM上关于OpenMP条目以及教材《MPI与OpenMP并行程序设计(C语言版)》:

    1. http://zh.wikipedia.org/wiki/OpenMP
    2. http://openmp.org/
    3. http://msdn.microsoft.com/en-us/library/tt15eb9t(v=vs.100).aspx
    4. 《MPI与OpenMP并行程序设计(C语言版)》第17章,Michael J. Quinn著,陈文光等译,清华大学出版社,2004

        注意,OpenMP目前最新版本为4.0.0,而VS2010仅支持OpenMP2.0(2002年版本),所以本文所讲的也是OpenMP2.0,本文注重使用OpenMP获得接近核心数的加速比,所以OpenMP2.0也足够了。

     

    2. 第一个OpenMP程序

    step 1: 新建控制台程序

    step 2: 项目属性,所有配置下“配置属性>>C/C++>>语言>>OpenMP支持”修改为是(/openmp),如下图:

    step 3: 添加如下代码:

    复制代码
     1 #include<omp.h>
     2 #include<iostream>
     3 int main()
     4 {
     5     std::cout << "parallel begin:\n";
     6     #pragma omp parallel
     7     {
     8         std::cout << omp_get_thread_num();
     9     }
    10     std::cout << "\n parallel end.\n";
    11     std::cin.get();
    12     return 0;
    13 }
    复制代码

    step 4: 运行结果如下图:

    可以看到,我的计算机是8核的(严格说是8线程的),这是我们实验室的小型工作站(至多支持24核)。

     

    3. “第一个OpenMP程序”幕后,并行原理

        OpenMP由Compiler Directives(编译指导语句)、Run-time Library Functions(库函数)组成,另外还有一些和OpenMP有关的Environment Variables(环境变量)、Data Types(数据类型)以及_OPENMP宏定义。之所以说OpenMP非常简单,是因为,所有这些总共只有50个左右,OpenMP2.0 Specification仅有100余页。第2节的“第一个OpenMP程序”的第6行“#pragma omp parallel”即Compiler Directive,“#pragma omp parallel”下面的语句将被多个线程并行执行(也即被执行不止一遍),第8行的omp_get_thread_num()即Run-time Library Function,omp_get_thread_num()返回当前执行代码所在线程编号。

        共享内存计算机上并行程序的基本思路就是使用多线程,从而将可并行负载分配到多个物理计算核心,从而缩短执行时间(同时提高CPU利用率)。在共享内存的并行程序中,标准的并行模式为fork/join式并行,这个基本模型如下图示:

    其中,主线程执行算法的顺序部分,当遇到需要进行并行计算式,主线程派生出(创建或者唤醒)一些附加线程。在并行区域内,主线程和这些派生线程协同工作,在并行代码结束时,派生的线程退出或者挂起,同时控制流回到单独的主线程中,称为汇合。对应第2节的“第一个OpenMP程序”,第4行对应程序开始,4-5行对应串行部分,6-9行对应第一个并行块(8个线程),10-13行对应串行部分,13行对应程序结束。

        简单来说,OpenMP程序就是在一般程序代码中加入Compiler Directives,这些Compiler Directives指示编译器其后的代码应该如何处理(是多线程执行还是同步什么的)。所以说OpenMP需要编译器的支持。上一小节的step 2即打开编译器的OpenMP支持。和Pthreads不同,OpenMP下程序员只需要设计高层并行结构,创建及调度线程均由编译器自动生成代码完成。

     

    4. Compiler Directives

    4.1 一般格式

    Compiler Directive的基本格式如下:

    #pragma omp directive-name [clause[ [,] clause]...]

    其中“[]”表示可选,每个Compiler Directive作用于其后的语句(C++中“{}”括起来部分是一个复合语句)。

    directive-name可以为:parallel, for, sections, single, atomic, barrier, critical, flush, master, ordered, threadprivate(共11个,只有前4个有可选的clause)。

    clause(子句)相当于是Directive的修饰,定义一些Directive的参数什么的。clause可以为:copyin(variable-list), copyprivate(variable-list), default(shared | none), firstprivate(variable-list), if(expression), lastprivate(variable-list), nowait, num_threads(num), ordered, private(variable-list), reduction(operation: variable-list), schedule(type[,size]), shared(variable-list)(共13个)。

        例如“#pragma omp parallel”表示其后语句将被多个线程并行执行,线程个数由系统预设(一般等于逻辑处理器个数,例如i5 4核8线程CPU有8个逻辑处理器),可以在该directive中加入可选的clauses,如“#pragma omp parallel num_threads(4)”仍旧表示其后语句将被多个线程并行执行,但是线程个数为4。

    4.2 详细解释

        本节的叙述顺序同我的另一篇博文:OpenMP编程总结表,读者可以对照阅读,也可以快速预览OpenMP所有语法。

        如果没有特殊说明,程序均在Debug下编译运行。

    parallel                                        

        parallel表示其后语句将被多个线程并行执行,这已经知道了。“#pragma omp parallel”后面的语句(或者,语句块)被称为parallel region。

        可以用if clause条件地进行并行化,用num_threads clause覆盖默认线程数:

    1 int a = 0;
    2 #pragma omp parallel if(a) num_threads(6)
    3 {
    4     std::cout << omp_get_thread_num();
    5 }

    int a = 7;
    #pragma omp parallel if(a) num_threads(6)
    {
        std::cout << omp_get_thread_num();
    }

    可以看到多个线程的执行顺序是不能保证的。

        private, firstprivate, shared, default, reduction, copyin clauses留到threadprivate directive时说。

    for                                        

        第2节的“第一个OpenMP程序”其实不符合我们对并行程序的预期——我们一般并不是要对相同代码在多个线程并行执行,而是,对一个计算量庞大的任务,对其进行划分,让多个线程分别执行计算任务的每一部分,从而达到缩短计算时间的目的。这里的关键是,每个线程执行的计算互不相同(操作的数据不同或者计算任务本身不同),多个线程协作完成所有计算。OpenMP for指示将C++ for循环的多次迭代划分给多个线程(划分指,每个线程执行的迭代互不重复,所有线程的迭代并起来正好是C++ for循环的所有迭代),这里C++ for循环需要一些限制从而能在执行C++ for之前确定循环次数,例如C++ for中不应含有break等。OpenMP for作用于其后的第一层C++ for循环。下面是一个例子:

    复制代码
    1 const int size = 1000;
    2 int data[size];
    3 #pragma omp parallel
    4 {
    5     #pragma omp for
    6     for(int i=0; i<size; ++i)
    7         data[i] = 123;
    8 }
    复制代码

    默认情况下,上面的代码中,程序执行到“#pragma omp parallel”处会派生出7和线程,加上主线程共8个线程(在我的机器上),C++ for的1000次迭代会被分成连续的8段——0-124次迭代由0号线程计算,125-249次迭代由1号线程计算,以此类推。可能你已经猜到了,具体C++ for的各次迭代在线程间如何分配可以由clause指示,它就是schedule(type[,size]),后面会具体说。

        如果parallel region中只包含一个for directive作用的语句,上面代码就是这种情况,此时可以将parallel和for“缩写”为parallel for,上面代码等价于这样:

    1 const int size = 1000;
    2 int data[size];
    3 #pragma omp parallel for
    4 for(int i=0; i<size; ++i)
    5     data[i] = 123;

        正确使用for directive有两个条件,第1是C++ for符合特定限制,否则编译器将报告错误,第2是C++ for的各次迭代的执行顺序不影响结果正确性,这是一个逻辑条件。例子如下:

    复制代码
    1 #pragma omp parallel num_threads(6)
    2 {
    3     #pragma omp for
    4     for(int i=0; i<1000000; ++i)
    5         if(i>999)
    6             break;
    7 }
    复制代码

    编译器报错如下:

    error C3010: “break”: 不允许跳出 OpenMP 结构化块

        schedule(type[,size])设置C++ for的多次迭代如何在多个线程间划分:

    1. schedule(static, size)将所有迭代按每连续size个为一组,然后将这些组轮转分给各个线程。例如有4个线程,100次迭代,schedule(static, 5)将迭代:0-4, 5-9, 10-14, 15-19, 20-24...依次分给0, 1, 2, 3, 0...号线程。schedule(static)同schedule(static, size_av),其中size_av等于迭代次数除以线程数,即将迭代分成连续的和线程数相同的等分(或近似等分)。
    2. schedule(dynamic, size)同样分组,然后依次将每组分给目前空闲的线程(故叫动态)。
    3. schedule(guided, size) 把迭代分组,分配给目前空闲的线程,最初组大小为迭代数除以线程数,然后逐渐按指数方式(依次除以2)下降到size。
    4. schedule(runtime)的划分方式由环境变量OMP_SCHEDULE定义。

    下面是几个例子,可以先忽略critical directive:

    复制代码
    1 #pragma omp parallel num_threads(3)
    2 {
    3     #pragma omp for
    4     for(int i=0; i<9; ++i){
    5         #pragma omp critical
    6         std::cout << omp_get_thread_num() << i << " ";
    7     }
    8 }
    复制代码

    上面输出说明0号线程执行0-2迭代,1号执行3-5,2号执行6-9,相当于schedule(static, 3)。

    复制代码
    1 #pragma omp parallel num_threads(3)
    2 {
    3     #pragma omp for schedule(static, 1)
    4     for(int i=0; i<9; ++i){
    5         #pragma omp critical 
    6         std::cout << omp_get_thread_num() << i << " ";
    7     }
    8 }
    复制代码

    复制代码
    1 #pragma omp parallel num_threads(3)
    2 {
    3     #pragma omp for schedule(dynamic, 2)
    4     for(int i=0; i<9; ++i){
    5         #pragma omp critical 
    6         std::cout << omp_get_thread_num() << i << " ";
    7     }
    8 }
    复制代码

        ordered clause配合ordered directive使用,请见ordered directive,nowait留到barrier directive时说,private, firstprivate, lastprivate, reduction留到threadprivate directive时说。

    sections                                        

        如果说for directive用作数据并行,那么sections directive用于任务并行,它指示后面的代码块包含将被多个线程并行执行的section块。下面是一个例子:

    复制代码
     1 #pragma omp parallel
     2 {
     3     #pragma omp sections
     4     {
     5         #pragma omp section
     6         std::cout << omp_get_thread_num();
     7         #pragma omp section
     8         std::cout << omp_get_thread_num();
     9     }
    10 }
    复制代码

    上面代码中2个section块将被2个线程并行执行,多个个section块的第1个“#pragma omp section”可以省略。这里有些问题,执行这段代码是总共会有多少个线程呢,“#pragma omp parallel”没有clause,默认是8个线程(又说的在我的机器上),2个section是被哪2个线程执行是不确定的,当section块多于8个时,会有一个线程执行不止1个section块。

        同样,上面代码可以“缩写”为parallel sections

    复制代码
    1 #pragma omp parallel sections
    2 {
    3     #pragma omp section
    4     std::cout << omp_get_thread_num();
    5     #pragma omp section
    6     std::cout << omp_get_thread_num();
    7 }
    复制代码

        nowait clause留到barrier directive时说,private, firstprivate, lastprivate, reduction clauses留到threadprivate directive时说。

    single                                        

        指示代码将仅被一个线程执行,具体是哪个线程不确定,例子如下:

    复制代码
    1 #pragma omp parallel num_threads(4)
    2 {
    3     #pragma omp single
    4     std::cout << omp_get_thread_num();
    5     std::cout << "-";
    6 }
    复制代码

    这里0号线程执行了第4 5两行代码,其余三个线程执行了第5行代码。

        nowait clause留到barrier directive时说,private, firstprivate, copyprivate clauses留到threadprivate directive时说。

    master                                        

        指示代码将仅被主线程执行,功能类似于single directive,但single directive时具体是哪个线程不确定(有可能是当时闲的那个)。

    critical                                        

        定义一个临界区,保证同一时刻只有一个线程访问临界区。观察如下代码及其结果:

    1 #pragma omp parallel num_threads(6)
    2 {
    3     std::cout << omp_get_thread_num() << omp_get_thread_num();
    4 }

    5号线程执行第3行代码时被2号线程打断了(并不是每次运行都可能出现打断)。

    1 #pragma omp parallel num_threads(6)
    2 {
    3     #pragma omp critical
    4     std::cout << omp_get_thread_num() << omp_get_thread_num();
    5 }

    这次不管运行多少遍都不会出现某个数字不是连续两个出现,因为在第4行代码被一个线程执行期间,其他线程不能执行(该行代码是临界区)。

    barrier                                        

        定义一个同步,所有线程都执行到该行后,所有线程才继续执行后面的代码,请看例子:

    复制代码
    1 #pragma omp parallel num_threads(6)
    2 {
    3     #pragma omp critical
    4     std::cout << omp_get_thread_num() << " ";
    5     #pragma omp critical
    6     std::cout << omp_get_thread_num()+10 << " ";
    7 }
    复制代码

    复制代码
    1 #pragma omp parallel num_threads(6)
    2 {
    3     #pragma omp critical
    4     std::cout << omp_get_thread_num() << " ";
    5     #pragma omp barrier
    6     #pragma omp critical
    7     std::cout << omp_get_thread_num()+10 << " ";
    8 }
    复制代码

    可以看到,这时一位数数字打印完了才开始打印两位数数字,因为,所有线程执行到第5行代码时,都要等待所有线程都执行到第5行,这时所有线程再都继续执行第7行及以后的代码,即所谓同步。

        再来说说for, sections, single directives的隐含barrier,以及nowait clause如下示例:

    复制代码
     1 #pragma omp parallel num_threads(6)
     2 {
     3     #pragma omp for
     4     for(int i=0; i<10; ++i){
     5         #pragma omp critical
     6         std::cout << omp_get_thread_num() << " ";
     7     }
     8     // There is an implicit barrier here.
     9     #pragma omp critical
    10     std::cout << omp_get_thread_num()+10 << " ";
    11 }
    复制代码

    复制代码
     1 #pragma omp parallel num_threads(6)
     2 {
     3     #pragma omp for nowait
     4     for(int i=0; i<10; ++i){
     5         #pragma omp critical
     6         std::cout << omp_get_thread_num() << " ";
     7     }
     8     // The implicit barrier here is disabled by nowait.
     9     #pragma omp critical
    10     std::cout << omp_get_thread_num()+10 << " ";
    11 }
    复制代码

    sections, single directives是类似的。

    atomic                                        

        atomic directive保证变量被原子的更新,即同一时刻只有一个线程再更新该变量(是不是很像critical directive),见例子:

    复制代码
    1 int m=0;
    2 #pragma omp parallel num_threads(6)
    3 {
    4     for(int i=0; i<1000000; ++i)
    5         ++m;
    6 }
    7 std::cout << "value should be: " << 1000000*6 << std::endl;
    8 std::cout << "value is: "<< m << std::endl;
    复制代码

    m实际值比预期要小,因为“++m”的汇编代码不止一条指令,假设三条:load, inc, mov(读RAM到寄存器、加1,写回RAM),有可能线程A执行到inc时,线程B执行了load(线程A inc后的值还没写回),接着线程A mov,线程B inc后再mov,原本应该加2就变成了加1。

        使用atomic directive后可以得到正确结果:

    复制代码
    1 int m=0;
    2 #pragma omp parallel num_threads(6)
    3 {
    4     for(int i=0; i<1000000; ++i)
    5         #pragma omp atomic
    6         ++m;
    7 }
    8 std::cout << "value should be: " << 1000000*6 << std::endl;
    9 std::cout << "value is: "<< m << std::endl;
    复制代码

        那用critical directive行不行呢:

    复制代码
    1 int m=0;
    2 #pragma omp parallel num_threads(6)
    3 {
    4     for(int i=0; i<1000000; ++i)
    5         #pragma omp critical
    6         ++m;
    7 }
    8 std::cout << "value should be: " << 1000000*6 << std::endl;
    9 std::cout << "value is: "<< m << std::endl;
    复制代码

        差别为何呢,显然是效率啦,我们做个定量分析:

    复制代码
     1 #pragma omp parallel num_threads(6)
     2 {
     3     for(int i=0; i<1000000; ++i) ;
     4 }
     5 int m;
     6 double t, t2;
     7 m = 0;
     8 t = omp_get_wtime();
     9 #pragma omp parallel num_threads(6)
    10 {
    11     for(int i=0; i<1000000; ++i)
    12         ++m;
    13 }
    14 t2 = omp_get_wtime();
    15 std::cout << "value should be: " << 1000000*6 << std::endl;
    16 std::cout << "value is: "<< m << std::endl;
    17 std::cout << "time(S): " << t2-t << std::endl;
    18 m = 0;
    19 t = omp_get_wtime();
    20 #pragma omp parallel num_threads(6)
    21 {
    22     for(int i=0; i<1000000; ++i)
    23         #pragma omp critical
    24         ++m;
    25 }
    26 t2 = omp_get_wtime();
    27 std::cout << "value should be: " << 1000000*6 << std::endl;
    28 std::cout << "value is: "<< m << std::endl;
    29 std::cout << "time of critical(S): " << t2-t << std::endl;
    30 m = 0;
    31 t = omp_get_wtime();
    32 #pragma omp parallel num_threads(6)
    33 {
    34     for(int i=0; i<1000000; ++i)
    35         #pragma omp atomic
    36         ++m;
    37 }
    38 t2 = omp_get_wtime();
    39 std::cout << "value should be: " << 1000000*6 << std::endl;
    40 std::cout << "value is: "<< m << std::endl;
    41 std::cout << "time of atomic(S): " << t2-t << std::endl;
    复制代码

    按照惯例,需要列出机器配置:Intel Xeon Processor E5-2637 v2 (4核8线程 15M Cache, 3.50 GHz),16GB RAM。上面代码需要在Release下编译运行以获得更为真实的运行时间(实际部署的程序不可能是Debug版本的),第一个parallel directive的用意是跳过潜在的创建线程的步骤,让下面三个parallel directives有相同的环境,以增加可比性。从结果可以看出,没有atomic clause或critical clause时运行时间短了很多,可见正确性是用性能置换而来的。不出所料,“大材小用”的critical clause运行时间比atomic clause要长很多。

    flush                                        

        指示所有线程对所有共享对象具有相同的内存视图(view of memory),该directive指示将对变量的更新直接写回内存(有时候给变量赋值可能只改变了寄存器,后来才才写回内存,这是编译器优化的结果)。这不好理解,看例子,为了让编译器尽情的优化代码,需要在Release下编译运行如下代码

    复制代码
     1 int data, flag=0;
     2 #pragma omp parallel sections num_threads(2) shared(data, flag)
     3 {
     4     #pragma omp section // thread 0
     5     {
     6         #pragma omp critical
     7         std::cout << "thread:" << omp_get_thread_num() << std::endl;
     8         for(int i=0; i<10000; ++i)
     9             ++data;
    10         flag = 1;
    11     }
    12     #pragma omp section // thread 1
    13     {
    14         while(!flag) ;
    15         #pragma omp critical
    16         std::cout << "thread:" << omp_get_thread_num() << std::endl;
    17         -- data;
    18         std::cout << data << std::endl;
    19     }
    20 }
    复制代码

    程序进入了死循环…… 我们的初衷是,用flag来做手动同步,线程0修改data的值,修改好了置flag,线程1反复测试flag检查线程0有没有修改完data,线程1接着再修改data并打印结果。这里进入死循环的可能原因是,线程1反复测试的flag只是读到寄存器中的值,因为线程1认为,只有自己在访问flag(甚至以为只有自己这1个线程),在自己没有修改内存之前不需要重新去读flag的值到寄存器。用flush directive修改后:

    复制代码
     1 int data=0, flag=0;
     2 #pragma omp parallel sections num_threads(2) shared(data, flag)
     3 {
     4     #pragma omp section // thread 0
     5     {
     6         #pragma omp critical
     7         std::cout << "thread:" << omp_get_thread_num() << std::endl;
     8         for(int i=0; i<10000; ++i)
     9             ++data;
    10         #pragma omp flush(data)
    11         flag = 1;
    12         #pragma omp flush(flag)
    13     }
    14     #pragma omp section // thread 1
    15     {
    16         while(!flag){
    17             #pragma omp flush(flag)
    18         }
    19         #pragma omp critical
    20         std::cout << "thread:" << omp_get_thread_num() << std::endl;
    21         #pragma omp flush(data)
    22         -- data;
    23         std::cout << data << std::endl;
    24     }
    25 }
    复制代码

    这回结果对了,解释一下,第10行代码告诉编译器,确保data的新值已经写回内存,第17行代码说,重新从内存读flag的值。

    ordered                                        

        使用在有ordered clause的for directive(或parallel for)中,确保代码将被按迭代次序执行(像串行程序一样),例子:

    复制代码
     1 #pragma omp parallel num_threads(8)
     2 {
     3     #pragma omp for ordered
     4     for(int i=0; i<10; ++i){
     5         #pragma omp critical
     6             std::cout << i << " ";
     7         #pragma omp ordered
     8         {
     9             #pragma omp critical
    10                 std::cout << "-" << i << " ";
    11         }
    12     }
    13 }
    复制代码

    只看前面有"-"的数字,是不是按顺序的,而没有"-"的数字则没有顺序。值得强调的是for directive的ordered clause只是配合ordered directive使用,而不是让迭代有序执行的意思,后者的代码是这样的:

    1 #pragma omp for ordered
    2 for(int i=0; i<10; ++i)
    3     #pragma omp ordered{
    4     ; // all the C++ for code
    5 }

    threadprivate                                      

        将全局或静态变量声明为线程私有的。为理解线程共享和私有变量,看如下代码:

    复制代码
    1 int a;
    2 std::cout << omp_get_thread_num() << ": " << &a << std::endl;
    3 #pragma omp parallel num_threads(8)
    4 {
    5     int b;
    6     #pragma omp critical
    7     std::cout << omp_get_thread_num() << ": " << &a << "  " << &b << std::endl;
    8 }
    复制代码

    记住第3-7行代码要被8个线程执行8遍,变量a是线程之间共享的,变量b是每个线程都有一个(在线程自己的栈空间)。

        怎么区分哪些变量是共享的,哪些是私有的呢。在parallel region内定义的变量(非堆分配)当然是私有的。没有特别用clause指定的(上面代码就是这样),在parallel region前(parallel region后的不可见,这点和纯C++相同)定义的变量是共享的,在堆(用new或malloc函数分配的)上分配的变量是共享的(即使是在多个线程中使用new或malloc,当然指向这块堆内存的指针可能是私有的),for directive作用的C++ for的循环变量不管在哪里定义都是私有的。

        好了,回到threadprivate directive,看例子:

    复制代码
     1 #include<omp.h>
     2 #include<iostream>
     3 int a;
     4 #pragma omp threadprivate(a)
     5 int main()
     6 {
     7     std::cout << omp_get_thread_num() << ": " << &a << std::endl;
     8     #pragma omp parallel num_threads(8)
     9     {
    10         int b;
    11         #pragma omp critical
    12         std::cout << omp_get_thread_num() << ": " << &a << "  " << &b << std::endl;
    13     }
    14     std::cin.get();
    15     return 0;
    16 }
    复制代码

        下面是最后几个没有讲的clauses:private, firstprivate, lastprivate, shared, default, reduction, copyin, copyprivate clauses,先看private clause:

    复制代码
    1 int a = 0;
    2 std::cout << omp_get_thread_num() << ": " << &a << std::endl;
    3 #pragma omp parallel num_threads(8) private(a)
    4 {
    5     #pragma omp critical
    6     std::cout << omp_get_thread_num() << ": *" << &a << "  " << a << std::endl;
    7 }
    复制代码

    private clause将变量a由默认线程共享变为线程私有的,每个线程会调用默认构造函数生成一个变量a的副本(当然这里int没有构造函数)。

        firstprivate clause和private clause的区别是,会用共享版本变量a来初始化。lastprivate clause在private基础上,将执行最后一次迭代(for)或最后一个section块(sections)的线程的私有副本拷贝到共享变量。shared clause和private clause相对,将变量声明为共享的。如下例子,其中的shared clause可以省略:

    复制代码
     1 int a=10, b=11, c=12, d=13;
     2 std::cout << "abcd's values: " << a << " " << b << " " << c << " " << d << std::endl;
     3 #pragma omp parallel for num_threads(8) \
     4     firstprivate(a) lastprivate(b) firstprivate(c) lastprivate(c) shared(d)
     5 for(int i=0; i<8; ++i){
     6     #pragma omp critical
     7     std::cout << "thread " << omp_get_thread_num() << " acd's values: "
     8         << a << " " << c << " " << d << std::endl;
     9     a = b = c = d = omp_get_thread_num();
    10 }
    11 std::cout << "abcd's values: " << a << " " << b << " " << c << " " << d << std::endl;
    复制代码

    每个线程都对a,b,c,d的值进行了修改。因为d是共享的,所以每个线程打印d前可能被其他线程修改了。parallel region结束,a的共享版本不变,b,c由于被lastprivate clause声明了,所以执行最后一次迭代的那个线程用自己的私有b,c更新了共享版本的b,c,共享版本d的值取决于那个线程最后更新d。

        default(shared|none):参数shared同于将所有变量用share clause定义,参数none指示对没有用private, shared, reduction, firstprivate, lastprivate clause定义的变量报错。

        reduction clause用于归约,如下是一个并行求和的例子:

    复制代码
     1 int sum=0;
     2 std::cout << omp_get_thread_num() << ":" << &sum << std::endl << std::endl;
     3 #pragma omp parallel num_threads(8) reduction(+:sum)
     4 {
     5     #pragma omp critical
     6     std::cout << omp_get_thread_num() << ":" << &sum << std::endl;
     7     #pragma omp for
     8     for(int i=1; i<=10000; ++i){
     9         sum += i;
    10     }
    11 }
    12 std::cout << "sum's valuse: " << sum << std::endl;
    复制代码

    可以看到变量sum在parallel region中是线程私有的,每个线程用自己的sum求一部分和,最后将所有线程的私有sum加起来赋值给共享版本的sum。除了“+”归约,/, |, &&等都可以作为归约操作的算法。

        copyin clause让threadprivate声明的变量的值和主线程的值相同,如下例子:

    复制代码
     1 #include<omp.h>
     2 #include<iostream>
     3 int a;
     4 #pragma omp threadprivate(a)
     5 int main()
     6 {
     7     a = 99;
     8     std::cout << omp_get_thread_num() << ": " << &a << std::endl << std::endl;
     9     #pragma omp parallel num_threads(8) copyin(a)
    10     {
    11         #pragma omp critical
    12         std::cout << omp_get_thread_num() << ": *" << &a << "  " << a << std::endl;
    13     }
    14     std::cin.get();
    15     return 0;
    16 }
    复制代码

    如果第9行代码修改为去掉copyin clause,结果如下:

        copyprivate clause让不同线程中的私有变量的值在所有线程中共享,例子:

    复制代码
    1 int a = 0;
    2 #pragma omp parallel num_threads(8) firstprivate(a)
    3 {
    4     #pragma omp single copyprivate(a)
    5     a = omp_get_thread_num()+10;
    6     #pragma omp critical
    7     std::cout << omp_get_thread_num() << ": *" << &a << "  " << a << std::endl;
    8 }
    复制代码

    能写在copyprivate里的变量必须是线程私有的,变量a符合这个条件,从上面结果可以看出,single directive的代码是被第4号线程执行的,虽然第4号线程赋值的a只是这个线程私有的,但是该新值将被广播到其他线程的a,这就造成了上面的结果。

    如果去掉copyprivate clause,结果变为:

    这次single directive的代码是被第0号线程执行的。

        呼,终于说完了,未尽事宜,见另一篇文章:OpenMP共享内存并行编程总结表

     

    6. 加速比

        加速比即同一程序串行执行时间除以并行执行时间,即并行化之后比串行的性能提高倍数。理论上,加速比受这些因素影响:程序可并行部分占比、线程数、负载是否均衡(可以查查Amdahl定律),另外,由于实际执行时并行程序可能存在的总线冲突,使得内存访问称为瓶颈(还有Cache命中率的问题),实际加速比一般低于理论加速比。

        为了看看加速比随线程数增加的变化情况,编写了如下代码,需要在Release下编译运行代码

    复制代码
     1 #include<iostream>
     2 #include<omp.h>
     3 int main(int arc, char* arg[])
     4 {
     5     const int size = 1000, times = 10000;
     6     long long int data[size], dataValue=0;
     7     for(int j=1; j<=times; ++j)
     8         dataValue += j;
     9  
    10     #pragma omp parallel num_threads(16)
    11         for(int i=0; i<1000000; ++i) ;
    12  
    13     bool wrong; double t, tsigle;
    14     for(int m=1; m<=16; ++m){
    15         wrong = false;
    16         t = omp_get_wtime();
    17         for(int n=0; n<100; ++n){
    18             #pragma omp parallel for num_threads(m)
    19             for(int i=0; i<size; ++i){
    20                 data[i] = 0;
    21                 for(int j=1; j<=times; ++j)
    22                     data[i] += j;
    23                 if(data[i] != dataValue)
    24                     wrong = true;
    25             }
    26         }
    27         t = omp_get_wtime()-t;
    28         if(m==1) tsigle=t;
    29         std::cout << "num_threads(" << m << ") rumtime: " << t << " s.\n";
    30         std::cout << "wrong=" << wrong << "\tspeedup: " << tsigle/t << "\tefficiency: " << tsigle/t/m << "\n\n";
    31     }
    32  
    33     std::cin.get();
    34     return 0;
    35 }
    复制代码

    可以看到,由于我们的程序是在操作系统层面上运行,而非直接在硬件上运行,上面的测试结果出现了看似不可思议的结果——效率竟然有时能大于1!最好的加速比出现在num_threads(8)时,为7.4左右,已经很接近物理核心数8了,充分利用多核原来如此简单。

    展开全文
  • OpenMP是一种应用于多处理器程序设计的并行编程处理方案,它提供了对于并行编程的高层抽象,只需要在程序中添加简单的指令,就可以编写高效的并行程序,而不用关心具体的并行实现细节,降低了并行编程的难度和复杂度...

    OpenMP是一种应用于多处理器程序设计的并行编程处理方案,它提供了对于并行编程的高层抽象,只需要在程序中添加简单的指令,就可以编写高效的并行程序,而不用关心具体的并行实现细节,降低了并行编程的难度和复杂度。也正因为OpenMP的简单易用性,它并不适合于需要复杂的线程间同步和互斥的场合。


    OpenCV中使用Sift或者Surf特征进行图像拼接的算法,需要分别对两幅或多幅图像进行特征提取和特征描述,之后再进行图像特征点的配对,图像变换等操作。不同图像的特征提取和描述的工作是整个过程中最耗费时间的,也是独立 运行的,可以使用OpenMP进行加速。


    以下是不使用OpenMP加速的Sift图像拼接原程序:

    1. #include "highgui/highgui.hpp"      
    2. #include "opencv2/nonfree/nonfree.hpp"      
    3. #include "opencv2/legacy/legacy.hpp"     
    4. #include "omp.h"  
    5.   
    6. using namespace cv;  
    7.   
    8. //计算原始图像点位在经过矩阵变换后在目标图像上对应位置    
    9. Point2f getTransformPoint(const Point2f originalPoint, const Mat &transformMaxtri);  
    10.   
    11. int main(int argc, char *argv[])  
    12. {  
    13.     float startTime = omp_get_wtime();  
    14.   
    15.     Mat image01 = imread("Test01.jpg");  
    16.     Mat image02 = imread("Test02.jpg");  
    17.     imshow("拼接图像1", image01);  
    18.     imshow("拼接图像2", image02);  
    19.   
    20.     //灰度图转换    
    21.     Mat image1, image2;  
    22.     cvtColor(image01, image1, CV_RGB2GRAY);  
    23.     cvtColor(image02, image2, CV_RGB2GRAY);  
    24.   
    25.     //提取特征点      
    26.     SiftFeatureDetector siftDetector(800);  // 海塞矩阵阈值    
    27.     vector<KeyPoint> keyPoint1, keyPoint2;  
    28.     siftDetector.detect(image1, keyPoint1);  
    29.     siftDetector.detect(image2, keyPoint2);  
    30.   
    31.     //特征点描述,为下边的特征点匹配做准备      
    32.     SiftDescriptorExtractor siftDescriptor;  
    33.     Mat imageDesc1, imageDesc2;  
    34.     siftDescriptor.compute(image1, keyPoint1, imageDesc1);  
    35.     siftDescriptor.compute(image2, keyPoint2, imageDesc2);  
    36.   
    37.     float endTime = omp_get_wtime();  
    38.     std::cout << "不使用OpenMP加速消耗时间: " << endTime - startTime << std::endl;  
    39.     //获得匹配特征点,并提取最优配对       
    40.     FlannBasedMatcher matcher;  
    41.     vector<DMatch> matchePoints;  
    42.     matcher.match(imageDesc1, imageDesc2, matchePoints, Mat());  
    43.     sort(matchePoints.begin(), matchePoints.end()); //特征点排序      
    44.                                                     //获取排在前N个的最优匹配特征点    
    45.     vector<Point2f> imagePoints1, imagePoints2;  
    46.     for (int i = 0; i < 10; i++)  
    47.     {  
    48.         imagePoints1.push_back(keyPoint1[matchePoints[i].queryIdx].pt);  
    49.         imagePoints2.push_back(keyPoint2[matchePoints[i].trainIdx].pt);  
    50.     }  
    51.   
    52.     //获取图像1到图像2的投影映射矩阵,尺寸为3*3    
    53.     Mat homo = findHomography(imagePoints1, imagePoints2, CV_RANSAC);  
    54.     Mat adjustMat = (Mat_<double>(3, 3) << 1.0, 0, image01.cols, 0, 1.0, 0, 0, 0, 1.0);  
    55.     Mat adjustHomo = adjustMat*homo;  
    56.   
    57.     //获取最强配对点在原始图像和矩阵变换后图像上的对应位置,用于图像拼接点的定位    
    58.     Point2f originalLinkPoint, targetLinkPoint, basedImagePoint;  
    59.     originalLinkPoint = keyPoint1[matchePoints[0].queryIdx].pt;  
    60.     targetLinkPoint = getTransformPoint(originalLinkPoint, adjustHomo);  
    61.     basedImagePoint = keyPoint2[matchePoints[0].trainIdx].pt;  
    62.   
    63.     //图像配准    
    64.     Mat imageTransform1;  
    65.     warpPerspective(image01, imageTransform1, adjustMat*homo, Size(image02.cols + image01.cols + 110, image02.rows));  
    66.   
    67.     //在最强匹配点左侧的重叠区域进行累加,是衔接稳定过渡,消除突变    
    68.     Mat image1Overlap, image2Overlap; //图1和图2的重叠部分       
    69.     image1Overlap = imageTransform1(Rect(Point(targetLinkPoint.x - basedImagePoint.x, 0), Point(targetLinkPoint.x, image02.rows)));  
    70.     image2Overlap = image02(Rect(0, 0, image1Overlap.cols, image1Overlap.rows));  
    71.     Mat image1ROICopy = image1Overlap.clone();  //复制一份图1的重叠部分    
    72.     for (int i = 0; i < image1Overlap.rows; i++)  
    73.     {  
    74.         for (int j = 0; j < image1Overlap.cols; j++)  
    75.         {  
    76.             double weight;  
    77.             weight = (double)j / image1Overlap.cols;  //随距离改变而改变的叠加系数    
    78.             image1Overlap.at<Vec3b>(i, j)[0] = (1 - weight)*image1ROICopy.at<Vec3b>(i, j)[0] + weight*image2Overlap.at<Vec3b>(i, j)[0];  
    79.             image1Overlap.at<Vec3b>(i, j)[1] = (1 - weight)*image1ROICopy.at<Vec3b>(i, j)[1] + weight*image2Overlap.at<Vec3b>(i, j)[1];  
    80.             image1Overlap.at<Vec3b>(i, j)[2] = (1 - weight)*image1ROICopy.at<Vec3b>(i, j)[2] + weight*image2Overlap.at<Vec3b>(i, j)[2];  
    81.         }  
    82.     }  
    83.     Mat ROIMat = image02(Rect(Point(image1Overlap.cols, 0), Point(image02.cols, image02.rows)));  //图2中不重合的部分    
    84.     ROIMat.copyTo(Mat(imageTransform1, Rect(targetLinkPoint.x, 0, ROIMat.cols, image02.rows))); //不重合的部分直接衔接上去    
    85.     namedWindow("拼接结果", 0);  
    86.     imshow("拼接结果", imageTransform1);  
    87.     imwrite("D:\\拼接结果.jpg", imageTransform1);  
    88.     waitKey();  
    89.     return 0;  
    90. }  
    91.   
    92. //计算原始图像点位在经过矩阵变换后在目标图像上对应位置    
    93. Point2f getTransformPoint(const Point2f originalPoint, const Mat &transformMaxtri)  
    94. {  
    95.     Mat originelP, targetP;  
    96.     originelP = (Mat_<double>(3, 1) << originalPoint.x, originalPoint.y, 1.0);  
    97.     targetP = transformMaxtri*originelP;  
    98.     float x = targetP.at<double>(0, 0) / targetP.at<double>(2, 0);  
    99.     float y = targetP.at<double>(1, 0) / targetP.at<double>(2, 0);  
    100.     return Point2f(x, y);  
    101. }  


    图像一:



    图像二:



    拼接结果 :



    在我的机器上不使用OpenMP平均耗时 4.7S。


    使用OpenMP也很简单,VS 内置了对OpenMP的支持。在项目上右键->属性->配置属性->C/C++->语言->OpenMP支持里选择是:



    之后在程序中加入OpenMP的头文件“omp.h”就可以了:

    1. #include "highgui/highgui.hpp"      
    2. #include "opencv2/nonfree/nonfree.hpp"      
    3. #include "opencv2/legacy/legacy.hpp"     
    4. #include "omp.h"  
    5.   
    6. using namespace cv;  
    7.   
    8. //计算原始图像点位在经过矩阵变换后在目标图像上对应位置    
    9. Point2f getTransformPoint(const Point2f originalPoint, const Mat &transformMaxtri);  
    10.   
    11. int main(int argc, char *argv[])  
    12. {  
    13.     float startTime = omp_get_wtime();  
    14.   
    15.     Mat image01, image02;  
    16.     Mat image1, image2;  
    17.     vector<KeyPoint> keyPoint1, keyPoint2;  
    18.     Mat imageDesc1, imageDesc2;  
    19.     SiftFeatureDetector siftDetector(800);  // 海塞矩阵阈值    
    20.     SiftDescriptorExtractor siftDescriptor;  
    21.     //使用OpenMP的sections制导指令开启多线程  
    22. #pragma omp parallel sections    
    23.     {  
    24. #pragma omp section    
    25.         {  
    26.             image01 = imread("Test01.jpg");  
    27.             imshow("拼接图像1", image01);  
    28.             //灰度图转换   
    29.             cvtColor(image01, image1, CV_RGB2GRAY);  
    30.             //提取特征点    
    31.             siftDetector.detect(image1, keyPoint1);  
    32.             //特征点描述,为下边的特征点匹配做准备      
    33.             siftDescriptor.compute(image1, keyPoint1, imageDesc1);  
    34.         }  
    35. #pragma omp section    
    36.         {  
    37.             image02 = imread("Test02.jpg");  
    38.             imshow("拼接图像2", image02);  
    39.             cvtColor(image02, image2, CV_RGB2GRAY);  
    40.             siftDetector.detect(image2, keyPoint2);  
    41.             siftDescriptor.compute(image2, keyPoint2, imageDesc2);  
    42.         }  
    43.     }  
    44.     float endTime = omp_get_wtime();  
    45.     std::cout << "使用OpenMP加速消耗时间: " << endTime - startTime << std::endl;  
    46.   
    47.     //获得匹配特征点,并提取最优配对       
    48.     FlannBasedMatcher matcher;  
    49.     vector<DMatch> matchePoints;  
    50.     matcher.match(imageDesc1, imageDesc2, matchePoints, Mat());  
    51.     sort(matchePoints.begin(), matchePoints.end()); //特征点排序      
    52.     //获取排在前N个的最优匹配特征点    
    53.     vector<Point2f> imagePoints1, imagePoints2;  
    54.     for (int i = 0; i < 10; i++)  
    55.     {  
    56.         imagePoints1.push_back(keyPoint1[matchePoints[i].queryIdx].pt);  
    57.         imagePoints2.push_back(keyPoint2[matchePoints[i].trainIdx].pt);  
    58.     }  
    59.   
    60.     //获取图像1到图像2的投影映射矩阵,尺寸为3*3    
    61.     Mat homo = findHomography(imagePoints1, imagePoints2, CV_RANSAC);  
    62.     Mat adjustMat = (Mat_<double>(3, 3) << 1.0, 0, image01.cols, 0, 1.0, 0, 0, 0, 1.0);  
    63.     Mat adjustHomo = adjustMat*homo;  
    64.   
    65.     //获取最强配对点在原始图像和矩阵变换后图像上的对应位置,用于图像拼接点的定位    
    66.     Point2f originalLinkPoint, targetLinkPoint, basedImagePoint;  
    67.     originalLinkPoint = keyPoint1[matchePoints[0].queryIdx].pt;  
    68.     targetLinkPoint = getTransformPoint(originalLinkPoint, adjustHomo);  
    69.     basedImagePoint = keyPoint2[matchePoints[0].trainIdx].pt;  
    70.   
    71.     //图像配准    
    72.     Mat imageTransform1;  
    73.     warpPerspective(image01, imageTransform1, adjustMat*homo, Size(image02.cols + image01.cols + 110, image02.rows));  
    74.   
    75.     //在最强匹配点左侧的重叠区域进行累加,是衔接稳定过渡,消除突变    
    76.     Mat image1Overlap, image2Overlap; //图1和图2的重叠部分       
    77.     image1Overlap = imageTransform1(Rect(Point(targetLinkPoint.x - basedImagePoint.x, 0), Point(targetLinkPoint.x, image02.rows)));  
    78.     image2Overlap = image02(Rect(0, 0, image1Overlap.cols, image1Overlap.rows));  
    79.     Mat image1ROICopy = image1Overlap.clone();  //复制一份图1的重叠部分   
    80.     for (int i = 0; i < image1Overlap.rows; i++)  
    81.     {  
    82.         for (int j = 0; j < image1Overlap.cols; j++)  
    83.         {  
    84.             double weight;  
    85.             weight = (double)j / image1Overlap.cols;  //随距离改变而改变的叠加系数    
    86.             image1Overlap.at<Vec3b>(i, j)[0] = (1 - weight)*image1ROICopy.at<Vec3b>(i, j)[0] + weight*image2Overlap.at<Vec3b>(i, j)[0];  
    87.             image1Overlap.at<Vec3b>(i, j)[1] = (1 - weight)*image1ROICopy.at<Vec3b>(i, j)[1] + weight*image2Overlap.at<Vec3b>(i, j)[1];  
    88.             image1Overlap.at<Vec3b>(i, j)[2] = (1 - weight)*image1ROICopy.at<Vec3b>(i, j)[2] + weight*image2Overlap.at<Vec3b>(i, j)[2];  
    89.         }  
    90.     }  
    91.     Mat ROIMat = image02(Rect(Point(image1Overlap.cols, 0), Point(image02.cols, image02.rows)));  //图2中不重合的部分    
    92.     ROIMat.copyTo(Mat(imageTransform1, Rect(targetLinkPoint.x, 0, ROIMat.cols, image02.rows))); //不重合的部分直接衔接上去    
    93.     namedWindow("拼接结果", 0);  
    94.     imshow("拼接结果", imageTransform1);  
    95.     imwrite("D:\\拼接结果.jpg", imageTransform1);  
    96.     waitKey();  
    97.     return 0;  
    98. }  
    99.   
    100. //计算原始图像点位在经过矩阵变换后在目标图像上对应位置    
    101. Point2f getTransformPoint(const Point2f originalPoint, const Mat &transformMaxtri)  
    102. {  
    103.     Mat originelP, targetP;  
    104.     originelP = (Mat_<double>(3, 1) << originalPoint.x, originalPoint.y, 1.0);  
    105.     targetP = transformMaxtri*originelP;  
    106.     float x = targetP.at<double>(0, 0) / targetP.at<double>(2, 0);  
    107.     float y = targetP.at<double>(1, 0) / targetP.at<double>(2, 0);  
    108.     return Point2f(x, y);  
    109. }  


    OpenMP中for制导指令用于迭代计算的任务分配,sections制导指令用于非迭代计算的任务分配,每个#pragma ompsection 语句会引导一个线程。在上边的程序中相当于是两个线程分别执行两幅图像的特征提取和描述操作。使用OpenMP后平均耗时2.5S,速度差不多提升了一倍。

    展开全文
  • 并行执行的代码在 #pragma omp parallel { } 代码块里,如果要将for循环拆分多个线程执行,则在for循环前加#pragma omp parallel for #include <stdio.h> #include <omp.h> int main() { #pr...

    需要加omp.h这个库,编译的时候加-fopenmp参数:在这里插入图片描述
    并行执行的代码在

    #pragma omp parallel 
        {
        }
    

    代码块里,如果要将for循环拆分多个线程执行,则在for循环前加#pragma omp parallel for

    #include <stdio.h>
    #include <omp.h>
    int main()
    {
    
        #pragma omp parallel for
        for (int i = 0; i < 6; i++) {
            printf("i=%d, 线程编号=%d\n", i, omp_get_thread_num());
         }
        return 0;
    }
    
    展开全文
  • 示例要求:在整数A和B之间...示例代码(需要在VS中开启OpenMP支持): 1 #include<iostream> 2 #include<time.h> 3 #include"omp.h" 4 5 int f(int i) { 6 int i4_huge = 2147483647; ...

    示例要求:在整数A和B之间找到符合条件的值X,使f(X)=C。

    示例代码(需要在VS中开启OpenMP支持):

     1 #include<iostream>
     2 #include<time.h>
     3 #include"omp.h"
     4 
     5 int f(int i) {
     6     int i4_huge = 2147483647;
     7     int j;
     8     int k;
     9     int value;
    10     value = i;
    11     for (j = 1; j <= 5; j++)
    12     {
    13         k = value / 127773;
    14         value = 16807 * (value - k * 127773) - k * 2836;
    15         if (value <= 0)
    16         {
    17             value = value + i4_huge;
    18         }
    19     }
    20     return value;
    21 }
    22 
    23 int search(int a, int b, int c)
    24 {
    25     int fi;
    26     int i;
    27     int j;
    28 
    29     j = -1;
    30 
    31     #pragma omp parallel for num_threads(6)
    32     for (i = a; i <= b; i++)
    33     {
    34         fi = f(i);
    35         if (fi == c)
    36         {
    37             j = i;
    38             break;
    39         }
    40     }
    41 
    42     return j;
    43 }
    44 
    45 
    46 int main()
    47 {
    48     int j;
    49     int a = 1;
    50     int b = 10000000;
    51     int c = 45;
    52     double wtime;
    53 
    54     clock_t start, finish;
    55     double totaltime;
    56     start = clock();
    57 
    58     j = search(a, b, c);
    59 
    60     finish = clock();
    61     totaltime = (double)(finish - start) / CLOCKS_PER_SEC;
    62     std::cout << "\n search运行时间为" << totaltime << "秒!" << std::endl;
    63     system("pause");
    64     return 0;
    65 }
    View Code

    并行算法的加速情况:

    加速前:

    加速后:

     

    转载于:https://www.cnblogs.com/GISQZC/p/9333312.html

    展开全文
  • 最近在编程时,由于涉及到大量for循环等遍历计算,需要了解并行编程知识,于是看了下OpenMP的相关教程; 何为OpenMP?以下为百科内容: OpenMP是由OpenMP Architecture Review Board牵头提出的,并已被广泛接受,...
  • OpenMP并行编程

    2018-03-21 23:45:05
    1 OpenMP 并行编程OpenMP 多核编程3 OpenMP中几个容易混淆的函数(线程数量/线程ID/线程最大数)以及并行区域线程数量的确定OpenMP的遇到parallel指令后创建的线程team的数量由如下过程决定:1. if子句的结果2....
  • openMP 并行编程

    2019-01-08 20:09:30
    OpenMP 入门教程: https://www.cnblogs.com/ospider/p/5265975.html openMP的一点使用经验: ... OpenMP共享内存并行编程详解: https://www.cnblogs.com/liangliangh/p/3565234.ht...
  • openMP编程探索3——并行区域编程

    千次阅读 2011-04-05 19:57:00
    在上一节中我们讲的是一个常用的并行编程方法(for的并行化),其实它只是并行编程的一个特例,只是它的地位较高,或者说它比其它并行化更重要。在本节中我们将讨论一般的并行区域编程
  • openMP编程探索4——并行区域编程

    千次阅读 2012-04-21 13:19:48
    我想了想threadprivate和private以及...2、private,可以限制变量为每个线程私有,但是他的生命周期是一次启动并行计算。 3、firstprivate,可以将穿行程序中的初值带进每个线程,变量为每个线程私有。生命
  • 使用OpenMP进行并行编程

    千次阅读 2018-11-05 11:40:09
    OpenMP 入门教程 ...在C++中使用openmp进行多线程编程 https://blog.csdn.net/acaiwlj/article/details/49818965 OpenMP中的同步和互斥 https://blog.csdn.net/dcrmg/article/details/...
  • C++ OpenMp并行编程

    2019-04-21 18:03:00
    基于OpenMp并行编程 功能:并行处理比较耗时的for循环 在OpenMP中,对for循环并行化的任务调度使用schedule子句来实现: 使用格式:schedule(type[,size]) type参数表示调度类型:static、dynamic、...
  • openMP 并行编程 基础

    2010-09-06 13:50:00
    从上篇文章中我们也可以发现OpenMP并行执行的程序要全部结束后才能执行后面的非并行部分的代码。这就是标准的并行模式fork/join式并行模式,共享存储式并行程序就是使用fork/join式并行的。 标准并行模式执行代码的...

空空如也

1 2 3 4 5 ... 20
收藏数 4,670
精华内容 1,868
关键字:

openmp