OpenMP学习

http://openmp.org/wp/

传统的单线程编程方式难以发挥多核CPU的强大功能,于是多核编程应运而生。多核编程可以认为是对多核环境下编程做了一些多线程抽象,提供一些简单的API,使得用户不必费太多精力来了解太多底层的知识。多核编程的工具有OpenMP和TBB,OpenMP支持的编程语言包括C、C++和Fortran,支持OpenMP的编译器包括Sun Studio、Intel Compiler、Microsoft Visual Studio、GCC。

在VS上配置openMP非常简单,总共分成2步:

(1)新建一个工程

(2)建立工程后,点击菜单栏->Project->Propertier,弹出菜单里,点击Configuration Properties->C/C++->Language->OpenMP Support,在下拉菜单里选择YES。

下面通过一个小例子说明openMP的易用性。

#include <iostream>
#include <time.h>
void test()
{
    int a = 0;
    for (int i=0;i<100000000;i++)
        a++;
}
int main()
{
    clock_t t1 = clock();
    for (int i=0;i<8;i++)
        test();
    clock_t t2 = clock();
    std::cout<<"time: "<<t2-t1<<std::endl;
}

下面用一句话把上面代码变成多核运行。

#include <iostream>
#include <time.h>
void test()
{
    int a = 0;
    for (int i=0;i<100000000;i++)
        a++;
}
int main()
{
    clock_t t1 = clock();
    #pragma omp parallel for
    for (int i=0;i<8;i++)
        test();
    clock_t t2 = clock();
    std::cout<<"time: "<<t2-t1<<std::endl;
}

在上面的代码里,我们没有额外include头文件,没有额外link库文件,只是在for循环前加了一句#pragma omp paralle for。而且且这段代码在单核机器上,或者编译器没有将openMP设为YES的机器上编译也不会报错,将自动忽略#pragma这行代码,然后按传统单核串行的方式编译运行!我们唯一要多做的一步,是从C:\Program Files\Microsoft Visual Studio XX.X\VC\redist\x86\Microsoft.VCxx.OPENMP和C:\Program Files\Microsoft Visual Studio XX.X\VC\redist\Debug_NonRedist\x86\Microsoft.VCxx.DebugOpenMP目录下分别拷贝vcompxxd.dll和vcommxx.dll文件到工程文件当前目录下。

对上面代码做一个简单的剖析。当编译器发现#pragm omp parallel for后,自动将下面的for循环分成N份(N为CPU核数),然后把每份指派给一个核去执行,而且多核之间并行执行。

下面谈谈竟态条伯的问题,当多个线程并行执行时,有可能多个线程同时对某变量进行了读写操作,从而导致不可预知的结果。比如数组求和,那么openMP怎么实现并行数组求和呢?下面给出一个基本的解决方案,该方案的思想是,首先生成一个数组sumArray,其长度为并行执行的线程的个数(默认情况下,该个数等于CPU的核数),在for循环里,让各个线程更新自己线程对应的sumArray里的无素,最后再将sumArray里的元素累加到sum里,如下:

#include <iostream>
#include <omp.h>
int main(){
    int sum = 0;
    int a[10] = {1,2,3,4,5,6,7,8,9,10};
    int coreNum = omp_get_num_procs();//获得处理器个数
    int* sumArray = new int[coreNum];//对应处理器个数,先生成一个数组
    for (int i=0;i<coreNum;i++)//将数组各元素初始化为0
        sumArray[i] = 0;
#pragma omp parallel for
    for (int i=0;i<10;i++)
    {
        int k = omp_get_thread_num();//获得每个线程的ID
        sumArray[k] = sumArray[k]+a[i];
    }
    for (int i = 0;i<coreNum;i++)
        sum = sum + sumArray[i];
    std::cout<<"sum: "<<sum<<std::endl;
    return 0;
}

需要注意的是,在上面代码里,用omp_get_num_procs()函数来获取处理器个数,用omp_get_thread_num函数来获得每个线程的ID,为了使用这两个函数,我们需要include <omp.h>。

上面的代码虽然达到了目的,但它产生了较多的额外操作,比如要先生成数组sumArray,最后还要用一个for循环将它的各元素累加起来,但openMP为我们提供了另一个工具,归约(reduction),见下面代码:

#include <iostream>
int main(){
    int sum = 0;
    int a[10] = {1,2,3,4,5,6,7,8,9,10};
#pragma omp parallel for reduction(+:sum)
    for (int i=0;i<10;i++)
        sum = sum + a[i];
    std::cout<<"sum: "<<sum<<std::endl;
    return 0;
}

上面代码里,我们在#pragma omp parallel for后面加上了reduction(+:sum),它的意思是告诉编译器:下面的for循环把自己分成多个线程跑,但每个线程都要保存变量sum的拷贝,循环结束后,所有线程把自己的sum累加起来作为最后的输出。

reduction虽然很方便,但它只支持一些基本操作,比如+、-、*、&、|、&&、||等。有些情况下,我们既要避免race condition,但涉及到的操作又超出了reduction的能力范围,这时就要用到openMP的另一个工具,critical。

#include <iostream>
int main(){
    int max = 0;
    int a[10] = {11,2,33,49,113,20,321,250,689,16};
#pragma omp parallel for
    for (int i=0;i<10;i++)
    {
        int temp = a[i];
#pragma omp critical
        {
            if (temp > max)
                max = temp;
        }
    }
    std::cout<<"max: "<<max<<std::endl;
    return 0;
}
上例中,for循环还是被自动分成N份来并行执行,但我们用#pragm omp critical 将if (temp>max) max = temp括起来,它的意思是:各个线程还是并行执行for里面的语句,但当执行到critical里面时,要注意有没有其他线程正在里面执行,如果有的话,要等其他线程执行完再去执行。这样就避免了race condition问题,但显而易见,它的执行速度会变低,因为可能存在线程等待的情况。

以上内容参考:http://www.cnblogs.com/yangyangcv/archive/2012/03/23/2413335.html


OpenMP API C/C++语法快速浏览

  • 编译器指令

编译器指令用以告诉编译器哪一段代码需要并行,所有的OpenMP编译器指令都以#pragma omp开始。就像其它编译器指令一样,在编译器不支持这些特征的时候OpenMP指令将被忽略。

#pragma omp <directive> [clause,[[,]clause...]

对directive(指令)而言clause(子句)是可选的,但子句可以影响到指令的行为。每一个指令有一系列适合它的子句,但有5个指令(master、cirticle、flush、ordered和atomic)不能使用子句。


(1)Parallel

parallel结构由一组线程组成,并行执行

#pragma omp paralle [clause[[,]clause]...]

     structured-block

clause:

    if(scalar-expression)

    num_threads(integer-expression)

    default(shared|none)  //控制在parallel或task结构中共享数据的默认共享属性

    private(list) //声明一个或多个列表是一个任务所私有的

    firstprivate(list)

    shared(list)  //声明一个或多个list项用于parallel或task结构中生成的任务共享

    copyin(list)//复制主线程中线程私有量到同组中的其他线程中

    reduction(operator:list)

(2)Loop

#pragma omp for [clause[[,]clause]...]

    for-loops

clause:

    private(list)

    firstprivate(list)

    lastprivate(list)

    reduction(operator:list)

    schedule(kind[,chunk_size])

   collapse(n)

    ordered

    nowait

kind:

--static: 循环根据chunk_size大小分成块,这些块以round-robin方式分配给线程

--dynamic: 每个线程执行完一块循环后,再请求下一个块

--guided:每个线程先执行一块循环后,再请求下一个块,在这个过程中块的大小在变小

--auto:由编译器或运行系统来决定

--runtime:调度程序 和块大小从run-sched-var ICV中取得

(3)Sections

sections是一种组织块

#pragma omp sections [clause[[,]clause]...]

{

    [#pragma omp section]

        structured-block

    [#pragma omp section]

       structured-block

...

}

clause:

    private(list)

    firstprivate(list)

    lastprivate(list)

    reduction(operator:list)

    nowait

(4)Single

用于指定一个结构块只被一个线程组中的一个线程招待

#pragma omp single [clause[[,]clause]...]

    structrued-block

clause:

    private(list)

    firstprivate(list)

    copyprivate(list)

    nowait

(5)Parallel Loop

loop在parrallel后的一种简短写法

#Parallel Loop

    for-loop

clause:

    除了nowait外,包括parallel和for中的所有clause

(6)Parallel Sections

是parallel后接sections的简写

(7)Task

指定一个详细的任务

#pragma omp task [clause[[,]clause]...]

    structured-block

clause:

    if(scalar-expression)

    final(scalar-expression)

    united

    default(shared|none)

    mergeable

    private(list)

    firstprivate(list)

    shared(list)

(8)Taskyield

表示当前任务可以暂停(suspended)以执行另一个任务

#pragma omp taskyield

(9)Master

指定一个结构块由主线程执行

#pragma omp master

(10)Critical

设置临界区

#pragma omp critical [(name)]

   structrued-block

(11)Barrier

(12)Taskwait

表示等待当前任务的子任务完成

#pragma omp taskwait

(13)Atomic

表示存储位置的更新是原子性的

#pragma omp atomic [read|write|update|capture]

   expression-stmt

#pragma omp atomic capture

    structured-block

(14)Flush

使线程的数据与内存一致

#pragma omp flush [(list)]

(15)Ordered

表示一个结构块以一个循环顺序进行

#pragma omp ordered

    structured-block

(16)Threadprivate

表示一个变量在是线程私有的

#pragma omp threadprivate(list)

  • 运行时库函数
OpenMP运行时库函数原本用来设置和获取执行环境相关的信息,它们当中包含一系列用以同步的API。要使用这些函数,必须包含OpenMP头文件omp.h。
(1)void omp_set_num_threads(int num_threads);
设置parallel区域中线程的个数
(2)int omp_get_num_threads(void)
返回当前组的线程个数
(3)int omp_get_max_threads(void)
返回一个组可以用的线程最大数
(4)int omp_get_thread_num(void)
返回现在线程ID
(5)int omp_get_num_pocs(void)
返回当前程序可以使用的处理器数量
(6)int omp_in_parallel(void)
如果调用程序由一个parallel区域封闭,返回true,否则返回false
(7)void omp_set_dynamic(int dynamic_threads)
开启或关闭动态调整
(8)int omp_get_dynamic(void)
返回dyn-var ICV的值
(9)void omp_set_nested(int nested)
开启或关闭嵌套循环
(10)int omp_get_nested(void)
(11)void omp_set_schedule(omp_sched_t kind, int modifier)
(12)void omp_get_schedule(omp_shced_t *kind,int *modifier)
(13)int omp_get_thread_limit(void)
返回OpenMP程序可用线程的最大值
(14)void omp_set_max_active_level(int max_level)
设置嵌套最大层数
(15)int omp_get_max_active_level(void)
(16)int omp_get_level(void)
(17)int omp_get_ancestor_thread_num(int level)
返回当前线程对应的上一层线程ID
(18)int omp_get_team_size(int level)
(19)int omp_get_active_level(void)
(20)int omp_in_final(void)
以上的函数称为环境变量函数。下面的函数用于同步的锁函数。
(21)void omp_init_lock(omp_lock_t *lock)
(22)void omp_init_nest_lock(omp_nest_lock_t *lock)
上面两个函数用于初始化一个OpenMP锁
(23)void omp_destroy_lock(omp_lock_t *lock)
(24)void omp_destroy_nest_lock(omp_nest_lock_t * lock)
下面几个函数用于上锁和解锁
(25)void omp_set_lock(omp_lock_t * lock)
(26)void omp_set_nest_lock(omp_lock_nest_lock)
(27)void omp_unset_lock(omp_lock_t *lock)
(28)void omp_unset_nest_lock(omp_lock_nest_lock *lock)
(29) int omp_test_lock(omp_lock_t *lock)
(30) int omp_test_nest_lock *lock)
下面是时间函数
(31)double omp_get_wtime(void)
(32)double omp_get_wtick(void)

环境变量

(1)OMP_SCHEDULE

(2)OMP_NUM_THREAD

(3)OMP_DYNAMIC

(4)OMP_PROC_BIND

(5)OMP_NESTED

(6)OMP_STACKSIZE

(7)OMP_WAIT_POLICY

(8)OMP_MAX_ACTIVE_LEVELS

(9)OMP_THREAD_LIMIT


你可能感兴趣的:(OpenMP学习)