OpenMP 简易入门笔记 含代码示例

2020/4/20 OpenMP

OpenMP 是基于共享存储体系的基于线程的并行编程模型。 采用 Fork-Join 并行执行方式。

OpenMP 几乎不需要安装额外的库,多数主流编译器均提供支持。当然,通常版本会比较非常旧。

@flowstart st=>start: Master thread process1=>operation: Fork process2=>operation: Jion process3=>operation: Fork process4=>operation: Jion e=>end: ...

st->process1->process2->process3->process4->e @flowend

  • Fork: 主线程创建一系列并行的线程,由这些线程来完成并行域的代码。
  • 当所有并行线程完成代码的执行后,它们或被同步或被中断,最后只剩下主线程在执行。

OpenMP 编程模型以线程为基础,通过编译制导指令制导并行化,有三种编程要素可以实现并行化控制,他们分别是

  • 编译制导
  • API 函数集
  • 环境变量

# 编译制导

  • OpenMP 的并行化是通过使用嵌入到 C/C++ 或 Fortran 源代码中的编译制导语句来实现。
  • 编译制导是对程序设计语言的扩展。
  • 通过对串行程序添加制导语句实现并行化。

编译制导语句由下列几部分组成:

  • 制导标识符 (!$OMP#pragma omp)
  • 制导名称 (parallelDO/forsection 等)
  • 子句 (privatesharedreductioncopyin 等) (用来说明并行域的附加信息。)

格式:制导标识符 制导名称 [子句,$\cdots$]

# 编译制导标识

制导是特殊的、仅用于特定编译器的源代码。OpenMP 制导标识: Fortran:

!$OMP
1

C/C++

#pragma omp
1

其中 !$OMP 为自由格式的 Fortran,固定格式不建议再使用了。

# 制导名称

常用制导名称 解释
parallel 用在一个结构块之前,表示这段代码将被多个线程并行执行;
DO/for 用于 DO/for 循环语句之前,表示将循环计算任务分配到多个线程中并行执行,以实现任务分担,必须由编程人员自己保证每次循环之间无数据相关性;
parallel for parallel 和 for 指令的结合,也是用在 for 循环语句之前,表示 for 循环体的代码将被多个线程并行执行,它同时具有并行域的产生和任务分担两个功能;
sections 用在可被并行执行的代码段之前,用于实现多个结构块语句的任务分担,可并行执行的代码段各自用 section 指令标出(注意区分 sections 和 section);
parallel sections parallel 和 sections 两个语句的结合,类似于 parallel for;
single 用在并行域内,表示一段只被单个线程执行的代码;
critical 用在一段代码临界区之前,保证每次只有一个 OpenMP 线程进入;
flush 保证各个 OpenMP 线程的数据影像的一致性;
barrier 用于并行域内代码的线程同步,线程执行到 barrier 时要停下等待,直到所有线程都执行到 barrier 时才继续往下执行;
atomic 用于指定一个数据操作需要原子性地完成;
master 用于指定一段代码由主线程执行;
threadprivate 用于指定一个或多个变量是线程专用,后面会解释线程专有和私有的区别。

详见 制导名称示例

# 子句

常用子句名称 解释
private 指定一个或多个变量在每个线程中都有它自己的私有副本;
firstprivate 指定一个或多个变量在每个线程都有它自己的私有副本,并且私有变量要在进入并行域或任务分担域时,继承主线程中的同名变量的值作为初值;
lastprivate 是用来指定将线程中的一个或多个私有变量的值在并行处理结束后复制到主线程中的同名变量中,负责拷贝的线程是 for 或 sections 任务分担中的最后一个线程;
reduction 用来指定一个或多个变量是私有的,并且在并行处理结束后这些变量要执行指定的归约运算,并将结果返回给主线程同名变量;
nowait 指出并发线程可以忽略其他制导指令暗含的路障同步;
num_threads 指定并行域内的线程的数目;
schedule 指定 DO/for 任务分担中的任务分配调度类型;
shared 指定一个或多个变量为多个线程间的共享变量;
ordered 用来指定 for 任务分担域内指定代码段需要按照串行循环次序执行;
copyprivate 配合 single 指令,将指定线程的专有变量广播到并行域内其他线程的同名变量中;
copyin n 用来指定一个 threadprivate 类型的变量需要用主线程同名变量进行初始化;
default 用来指定并行域内的变量的使用方式,缺省是 shared。

详见 子句示例

# 制导名称示例

# parallel 并行域制导

一个并行域就是一个能被多个线程并行执行的程序段

Fortran:

!$OMP PARALLEL [clauses]
    BLOCK
!$OMP END PARALLEL
1
2
3

C/C++:

#pragma omp parallel [clauses]
{
    BLOCK
}
1
2
3
4
  • 在并行域结尾有一个隐式同步(barrier)。
  • 子句(clause)用来说明并行域的附加信息。
  • 在 Fortran 语言中,子句间用逗号或空格分隔;C/C++ 子句间用空格分开。

# DO/for 制导

制导可以出现在并行域内部,并表明任务如何在多个线程间分配,OpenMP 任务划分制导包括:

  • 并行 DO/for 循环制导
  • 并行 SECTIONS 制导
  • SINGLE 和 MASTER 制导
  • 其它制导

并行 DO/for 循环制导用来将循环划分成多个块,并分配给各线程并行执行。

Fortran:

!$OMP DO[clauses]
  DO 循环
!$OMP END DO
1
2
3

C/C++:

#pragma omp for [clauses]
  for 循环
1
2

并行 DO/for 循环有时需要 PRIVATE 和 FIRSTPRIVARE 子句;循环变量是私有的。

# parallel DO/for 制导

可以将并行域和 DO/for 制导结合成单一的简单形式:

Fortran:

!$OMP PARALLEL [clauses]
!$OMP DO[clauses]
    循环体
!$OMP END DO
!$OMP END PARALLEL
1
2
3
4
5

合并后形式:

!$OMP PARALLEL DO[clauses]
    循环体
!$OMP END PARALLEL DO
1
2
3

同样地,C/C++:合并后形式

#pragma omp parallel for [clauses]
{
    循环体
}
1
2
3
4

# sections 制导

任务分配区 (work-sharing sections) 可以使 OpenMP 编译器和运行时库将应用程序中标出的结构化块 (block) 分配到并行区域的一组线程上

Fortran:

!$OMP SECTIONS[clauses]
[!$OMP SECTION]
    block
[!$OMP SECTION]
    block
    .......
!$OMP END SECTIONS
1
2
3
4
5
6
7

C/C++:

$pragma sections[clauses]
{
  [ $pragma section]
      block
  [ $pragma section]
      block
      .......
}
1
2
3
4
5
6
7
8

说明:

  • 各结构化块在各线程间并行执行:
  • sections 制导可以带有 PRIVATE、FIRSTPRIVATE 和其它子句;
  • 每个 section 必须包含一个结构体。

# parallel sections 制导

将并行域和 SECTIONS 制导结合成单一的简单形式:

Fortran:

$OMP PARALLEL SECTIONS[clauses]
    ......
$OMP END PARALLEL SECTIONS
1
2
3

C/C++:

$pragma parallel sections[clauses]
    ......
$pragma end parallel sections
1
2
3

# single 制导

结构体代码仅由一个线程执行;并由首先执行到该代码的线程执行;其它线程等待直至该结构块被执行完。

Fortran:

!OMP SINGLE [clauses]
    block
!OMP END SINGLE
1
2
3

C/C++:

#pragma omp single [clauses]
{
    structure block
}
1
2
3
4

# ordered 制导

指定循环按照迭代顺序执行,一个迭代只能执行一个 ordered 制导区域

# master 制导

结构体代码仅由主线程执行;其它线程跳过并继续执行;通常用于 I/O;

# barrier 制导

barrier 是 OpenMP 用于线程同步的一种方法

说明:

  • 在所有的线程到达之前,没有线程可以提前通过一个 barrier;
  • 在 DO/FOR、SECTIONS 和 SINGLE 制导后,有一个隐式 barrier 存在;
  • 要么所有线程遇到 barrier;要么没有线程遇到 barrier,否则会出现死锁。

# critical 制导

critical(临界段)可以保护共享变量的更新,避免数据竞争,制导内的代码段仅能有一个线程执行

Fortran:

!$OMP CRITICAL[(name)]
    block
!$OMP END CRITICAL[(name)]
1
2
3

C/C++:

#pragma omp critical[(name)]
    structure block
1
2

说明

  • Critical 制导在某一时刻仅能被一个线程执行;
  • Critical 制导可用来保护对共享变量的修改;
  • 在 Fortran 中,前后两个 name 必须一致;
  • 如果 name 被省略,一个空(null)的 name 被假定。

# atomic 制导

atomic 编译制导表明一个特殊的存储单元只能原子更新,而不允许让多个线程同时去写。主要用来保证操作被安全的执行。

Fortran:

!$OMP ATOMIC
statement
1
2

C/C++:

#pragma omp atomic
statement
1
2

说明

在 fortran 中,statement 必须是下列形式之一:
x=x op exprx=expr op xx=intr(x, expr)x=intr(expr,x) 其中:
op+-*/.and..or..eqv.、或 .neqv. 之一;
intrMAXminIANDIORIEOR 之一。

在 C/C++ 中,statement 必须是下列形式之一:
x binop=exprx++x--++x、或 --x
其中:
binop 是二元操作符:+-*/&^<<>> 之一。

ATOMIC 编译指导的好处是允许并行的更新数组内的不同元素;而使用临界制导时数组元素的更新是串行的;无论何时,当需要在更新共享存储单元的语句中避免数据竞争,应该先使用 atomic,然后再使用临界段。

# flush 制导

flush 语句是用来确保执行中存储器中的数据一致的同步点。保证一个变量从内存中的读/写

Fortran:

!$OPM FLUSH[(list)]
1

C/C++:

#prgma omp flush [(list)]
1

FLUSH 制导

  • Barrier
  • Parallel、critical、ordered 的入口和退出
  • omp_set_lock, omp_unset_lock
  • omp_test_lock, omp_set_nest_lock,
  • omp_unset_nest_lock, omp_test_nest_lock
  • Atomic 中的变量

# threadprivate 制导

threadprivate 制导指定复制变量,每个线程都有自己的副本(私有)。threadprivate 指令是声明性指令。

# 子句示例

# num_threads 子句

在 OpenMP(Fortran、C/C++) 提供了 num_threads 子句设定线程数。
num_threads 子句用来指定并行域内使用线程的个数,随后的其它并行域不受此影响。

说明: 在 num_threads 中提供的值将取代环境变量 OMP_NUM_THREADS 的值(或由 omp_set_num_threads() 设定的值)
例:#pragma omp parallel num_threads(2)

num_threads 子句的优先权高于库例程 omp_set_num_threads 和环境变量 NMP_NUM_THREADS

# if 子句

The semantics of an if clause are described in the section on the construct to which it applies. The if clause directive-name-modifier names the associated construct to which an expression applies, and is particularly useful for composite and combined constructs.

Fortran C/C++:

if([ directive-name-modifier :] scalar-logical-expression)
1

The effect of the if clause depends on the construct to which it is applied. For combined or composite constructs, the if clause only applies to the semantics of the construct named in the directive-name-modifier if one is specified. If no directive-name-modifier is specified for a combined or composite construct then the if clause applies to all constructs to which an if clause can apply.

# shared/privated 子句

并行域内的变量,可以通过子句说明为公有或私有;在编写多线程程序时,确定哪些数据的公有或私有非常重要:

  • Fortran:
    • SHARED(list)
    • PRIVATE(list)
    • DEFAULT(SHARED|PRIVATE|NONE)
  • C/C++:
    • shared(list)
    • private(list)
    • default(shared|private|none)

# reduction 子句

用来从相关的操作(+, * 等)中产生一个单一值;

# Fortran:
REDUCTION(op:list)
# C/C++:
reduction(op:list)
1
2
3
4

在 reduction 子句中,编译器为每个线程创建变量的私有副本。当循环完成后,将这些值处理在一起并把结果放到原始的变量中; Reduction 中的 op 操作必须满足算术结合律和交换律。

# firstprivate 子句

该子句使并行域内私有变量的初始值通过 master 线程的值初始化。

格式:firstprivate(变量列表)

# lastprivate 子句

该子句将使得 DO/for 循环制导内私有变量的最后值赋值给 master 线程的变量。

# opyin 子句

将主线程的 threadprivate 变量广播给其它线程的 threadprivate 变量

# Nowait 子句

NOWAIT 子句可以除去隐藏在循环、SECTIONS 或并行区后的同步

说明:

  • 使用 NOWAIT 时要特别小心,有可能导致不可确定的 bug;
  • 在有些地方使用 NOWAIT 可能是好的代码形式,并且显式的使用 barrier

# schedule 子句

该子句给出迭代循环划分后的块大小和线程执行的块范围: Fortran:

SCHEDULE(kind[, chunksize])
1

C/C++:

schedule(kind[, chunksize])
1

其中:

  • kind 为 STATIC, DYNAMIC 或 RUNTIME
  • chunksize 是一个整数表达式

子句说明:

  • schedule(STATIC [, chunksize]):
    • 省略 chunksize,迭代空间被划分成(近似)相同大小的区域,每个线程被分配一个区域;
    • 如果 chunksize 被指明,迭代空间被划分为 chunksize 大小,然后被轮转的分配给各个线程
  • schedule(DYNAMIC [, chunksize]):
    • 划分迭代空间为 chunksize 大小的区间,然后基于先来先服务方式分配给各线程;
    • 当省略 chunksize 时,其默认值为 1。
  • schedule (GUIDED [, chunksize]):
    • 类似于 DYNAMIC 调度,但区间开始大,然后迭代区间越来越少,循环区间的划分是基于类似下列公式完成的(不同的编译系统可能不同):Sk=[Rk2N]S_k=[\frac{R_k}{2N}],其中 N 是线程个数,SkS_k 表示第 k 块的大小,RkR_k 是剩余下未被调度的循环迭代次数。
    • chunksize 说明最小的区间大小。当省略 chunksize 时,其默认值为 1。
  • schedule(RUNTIME)
    • 调度选择延迟到运行时,调度方式取决于环境变量 OMP_SCHEDULE 的值,例如:export OMP_SCHEDULE="DYNAMIC, 4"
    • 使用 RUNTIME 时,指明 chunksize 是非法的;

详细解读位于 OpenMP 任务调度 一节

# 运行库函数

OpenMP 标准定义了一个应用程序编程接口来调用库中的多个函数。 有时需要得到线程数和线程号,这在控制不同线程执行不同的功能代码时特别有用。

函数名 函数作用
omp_in_parallel 判断当前是否在并行域中
omp_get_thread_num 返回线程号
omp_set_num_threads 设置后续并行域中的线程格式
omp_get_num_threads 返回当前并行区域中的线程数
omp_get_max_threads 获取并行域可用的最大线程数目
omp_get_num_procs 返回系统中处理器的个数
omp_get_dynamic 判断是否支持动态改变线程数目
omp_set_dynamlc 启用或关闭线程数目的动态改变
omp_get_nested 判断系统是否支特并行嵌套
omp_set_nested 启用或关闭并行嵌套
omp_get_wtime 返回当前的时钟时间
omp_get_wtick 返回时钟精度

# 得到线程队列中的线程数

Fortran:

interger function OMP_GET_NUM_THREADS ()
1

C/C++:

#include<omp.h>
int omp_get_num_threads()
1
2

# 得到执行线程的线程号

Fortran:

Interger function OMP_GET_THREAD_NUM ()
1

C/C++:

#include<omp.h>
int omp_get_thread_num()
1
2

# 设定执行线程的数量

使用运行库函数:

Fortran:

routine OMP_SET_NUM_THREADS ( )
1

C/C++:

#include<omp.h>
omp_set_num_threads()
1
2

在制导语句中通过 NUM_THREADS 设定。

通过环境变量 OMP_NUM_THREADS 设定。

# 返回当前的时钟时间(相对于任意原点)

Fortran:

DOUBLE PRECISION FUNCTION OMP_GET_WTIME()
1

C/C++:

double omp_get_wtime(void); 
1

# 返回时钟精度

Fortran:

DOUBLE PRECISION FUNCTION OMP_GET_WTICK()
1

C/C++:

double omp_get_wtick(void);
1

# OpenMP 环境变量

OpenMP 提供环境变量用来控制并行代码的执行

例如:

  1. OMP_NUM_THREADS:设定最大线程数。
    export OMP_NUM_THREADS=4
  2. OMP_SCHEDULE:设定 DO/for 循环调度方式环境变量。
    export OMP_SCHEDULE="DYNAMIC,4"
  3. OMP_DYNAMIC:确定是否动态设定并行域执行的 线程数,其值为 FALSETRUE
    export OMP_DYNAMIC=TRUE
  4. OMP_NESTED:指出是否可以并行嵌套。

# OpenMp 任务调度

OpenMP 中,任务调度主要用于并行的 DO/for 循环中,当循环中每次迭代的计算量不相等时,如果简单地给各个线程分配相同次数的迭代的话,会造成各个线程计算负载不均衡,这会使得有些线程先执行完,有些后执行完,造成某些 CPU 核空闲,影响程序性能。OpenMP 提供了 schedule 子句来实现任务的调度。

schedule 子句格式:schedule(type,[size])

  • 参数 type 是指调度的类型,可以取值为 staticdynamicguidedruntime 四种值。其中 runtime 允许在运行时确定调度类型,因此实际调度策略只有前面三种。
  • 参数 size 表示每次调度的迭代数量,必须是整数。该参数是可选的。当 type 的值是 runtime 时,不能够使用该参数。

# 静态调度 static

大部分编译器在没有使用 schedule 子句的时候,默认是 static 调度。static 在编译的时候就已经确定了,那些循环由哪些线程执行。假设有 n 次循环迭代,t 个线程,那么给每个线程静态分配大约 nt\frac{n}{t} 次迭代计算。nt\frac{n}{t} 不一定是整数,因此实际分配的迭代次数可能存在差 1 的情况。

在不使用 size 参数时,分配给每个线程的是 nt\frac{n}{t} 次连续的迭代,若循环次数为 10,线程数为 2,则线程 0 得到了 0~4 次连续迭代,线程 1 得到 5~9 次连续迭代。

当使用 size 时,将每次给线程分配 size 次迭代。若循环次数为 10,线程数为 2,指定 size 为 2 则 0、1 次迭代分配给线程 0,2、3 次迭代分配给线程 1,以此类推。

# 动态调度 dynamic

动态调度依赖于运行时的状态动态确定线程所执行的迭代,也就是线程执行完已经分配的任务后,会去领取还有的任务(与静态调度最大的不同,每个线程完成的任务数量可能不一样)。由于线程启动和执行完的时间不确定,所以迭代被分配到哪个线程是无法事先知道的。

当不使用 size 时,是将迭代逐个地分配到各个线程。当使用 size 时,逐个分配 size 个迭代给各个线程,这个用法类似静态调度。

# 启发式调度 guided

采用启发式调度方法进行调度,每次分配给线程迭代次数不同,开始比较大,以后逐渐减小。开始时每个线程会分配到较大的迭代块,之后分配到的迭代块会逐渐递减。迭代块的大小会按指数级下降到指定的 size 大小,如果没有指定 size 参数,那么迭代块大小最小会降到 1。

size 表示每次分配的迭代次数的最小值,由于每次分配的迭代次数会逐渐减少,少到 size 时,将不再减少。具体采用哪一种启发式算法,需要参考具体的编译器和相关手册的信息。

# 调度方式总结

静态调度 static:每次哪些循环由那个线程执行时固定的,编译调试。由于每个线程的任务是固定的,但是可能有的循环任务执行快,有的慢,不能达到最优。

动态调度 dynamic:根据线程的执行快慢,已经完成任务的线程会自动请求新的任务或者任务块,每次领取的任务块是固定的。

启发式调度 guided:每个任务分配的任务是先大后小,指数下降。当有大量任务需要循环时,刚开始为线程分配大量任务,最后任务不多时,给每个线程少量任务,可以达到线程任务均衡。

# LOCK 例程

Fortran:

Subroutine OMP_INIT_LOCK(VAR)
Subroutine OMP_SET_LOCK(VAR)
LOGICAL FUNCTION OMP_TEST_LOCK(VAR)
Subroutine OMP_UNSET_LOCK(VAR)
Subroutine OMP_DESTROY_LOCK(VAR)
1
2
3
4
5

其中变量是一个作为地址的整数

C/C++:

#include<OMP.h>
void omp_init_lock(omp_lock_t *lock);
void omp_set_lock(omp_lock_t *lock);
int omp_test_lock(omp_lock_t *lock);
void omp_unset_lock(omp_lock_t *lock);
void omp_detroy_lock(omp_lock_t *lock);
1
2
3
4
5
6

# OpenMP 并行注意的问题

  • 数据竞争问题;
  • 线程间同步;
  • 并行执行的程序比例及其可扩展性;
  • 共享内存或伪共享内存引起的访存冲突;

在 DO/for 循环中插入 OpenMP 指导前,首先要解决的问题是检查并重构热点循环,确保没有循环迭代相关; 优良的并行算法和精心调试是好的性能的保证,糟糕的算法即使使用手工优化的汇编语言来实现,也无法获得好的性能;

创建在单核或单处理器上出色运行的程序同创建在多核或多处理器上出色运行的程序是不同的; 可以借助一些工具,例 Intel VtuneTM 性能分析工具,其提供了一个 Intel 线程监测器。

# 数据竞争问题

下面的循环无法正确执行:

#pragma omp parallel for
for(k=0;k<100;k++)
{ x=array[k];
array[k]=do_work(x);
}
1
2
3
4
5

正确的方式:直接声明为私有变量

#pragma omp parallel for
private(x)
for(k=0;k<100;k++)
{ x=array[k];
array[k]=do_work(x);
}
1
2
3
4
5
6

在 parallel 结构中声明变量,这样的变量是私有的。

#pragma omp parallel for
for(k=0;k<100;k++)
{
int x;
x=array[k];
array[k]=do_work(x);
}
1
2
3
4
5
6
7

# 循环依赖(Loop Dependency)

了解循环依赖的好处

  • OpenMP 并行

    • SIMD: Vectorization(MMX, SSE, SSE2)
    • ILP: Instruction level parallelism
  • 循环依赖包括

    • 流依赖(Flow Dependency)
    • 反依赖(Anti-Dependency)
    • 写依赖(Output Dependency)
    • 迭代内依赖(Intra-Iteration Dependency)

# 流依赖(Flow Dependency)

跨迭代的写后读

for(j=1; j<MAX; j++){
    A[j] = A[j-1];
}
1
2
3

# 反依赖(Anti-Dependency)

跨迭代的读后写

for (j=1; j<MAX; j++){
    A[j] = A[j+1];
}
1
2
3

# 写依赖(Output Dependency)

跨迭代的写相关

for (j=1; j<MAX; j++){
    A[j] = B[j];
    A[j+1] = C[j];
}
1
2
3
4

# 迭代内依赖(Intra-Iteration Dependency)

一个迭代内的相关会破坏 ILP,可能被编译器自动删除

k = 1;
for (j=1 ; j<MAX; j++){
    A[j] = A[j] + 1;
    B[k] = A[k] + 1;
    k = k + 2;
}
1
2
3
4
5
6

# 归纳变量删除

一般是循环中,其后续值形成一个算术级数的变量。除了循环控制变量以外的一些变量,也遵循与循环控制变量类似的模式。

用循环控制变量 (j) 来替换归纳变量

i1 = 0;
i2 = 0;
for (j=0; j<MAX; j++){
    i1 = i1 + 1;
    B[i1] = ...;
    i2 = i2 + j;
    A[i2] = ...;
}
1
2
3
4
5
6
7
8

=>

for (j=0; j<MAX; j++){
    B[j] = ...;
    A[(j*j + j)/2] = ...;
}
1
2
3
4

# Reduction 变量

通过结合操作来收集数组的数据并存入标量

for(j=0; j<MAX; j++)
    sum = sum + c[j];
1
2
  1. 利用结合操作来计算部分和,或者局部最大值到私有空间
  2. 合并得到的部分结果到共享空间,此时需要注意同步

# 针对 Reduction 的优化

do j=1,n
a(j) = a(j-1) + b(j)
enddo
1
2
3

类似循环,当前迭代需要上一个迭代生成的数据很难并行化

# Data ambiguity

Void func(int *a, int *b){
   for(j=0;j<MAX;j++){
      a[j] = b[j];
   }
}
1
2
3
4
5

编译器假设数组 a 和 b 是有重叠的,不进行 simd 优化。 需要加 #pragma ivdep

# 函数调用

for (j=0; j<MAX; j++){
   compute(a[j], b[j]);
   a[j][1] = sin(b[j]);
}
1
2
3
4
  • 函数调用会阻止 ILP 优化
  • 许多库函数调用可能不是线程安全的,需要查手册进行确认,比如
    • 内存分配
    • 随机数生成
    • I/O 函数

# Loop 相关的简单测试

反转循环的顺序,如果结果是没有变化,那么该循环是 loop independence

需要注意归纳变量

for (j=0; j<MAX; j++){
    ...
}
For(j=MAX;j>=0;j--) {
    ...
}
1
2
3
4
5
6

# 向量化

Linux

  • -vec-reportn

Intel 诊断用编译器选项

  • n=0: No diagnostic information
  • n=1: (Default) loop successfully vectorized
  • n=2: Loop not vectorized – and the reason why not
  • n=3: Adds dependency information
  • n=4: Reports only non-vectorized loops
  • n=5: Reports only non-vectorized loops and add dependency info

本文为作者搜集互联网资料的整合,包括但不限于
OpenMP 并行编程 - 中科院计算机网络信息中心超级计算中心。原文链接 (opens new window)
「ArrowYL」的原创文章,遵循 CC 4.0 BY-SA 版权协议。原文链接 (opens new window)
官方文档:链接 (opens new window)
官方教程:链接 (opens new window)

Last Updated: 2023-10-29T08:26:04.000Z