Faiss(15):OpenMP并行编程

1. 说明

在上一篇分析CPU search过程的文档中,发现Faiss为提高搜索效率,缩短搜索时间,应用了OpenMP并行运算的功能,这一篇文档针对IndexIVF.cpp->search_preassigned函数中使用的OpenMP命令进行分析。

2. 概述

OpenMP是一种用于共享内存并行系统的多线程程序设计方案,支持的编程语言包括C、C++和Fortran。OpenMP提供了对并行算法的高层抽象描述,特别适合在多核CPU机器上的并行程序设计。编译器根据程序中添加的pragma指令,自动将程序并行处理,使用OpenMP降低了并行编程的难度和复杂度。当编译器不支持OpenMP时,程序会退化成普通(串行)程序。程序中已有的OpenMP指令不会影响程序的正常编译运行。

OpenMP执行模式

OpenMP采用fork-join的执行模式。开始的时候只存在一个主线程,当需要进行并行计算的时候,派生出若干个分支线程来执行并行任务。当并行代码执行完成之后,分支线程会合,并把控制流程交给单独的主线程。

一个典型的fork-join执行模型的示意图如下:
在这里插入图片描述OpenMP编程模型以线程为基础,通过编译制导指令制导并行化,有三种编程要素可以实现并行化控制,他们分别是编译命令、API函数集和环境变量。

3. 编译命令

编译制导指令以#pragma omp 开始,后边跟具体的功能指令,格式如:#pragma omp 指令[子句[,子句] …]。

1. #pragma omp parallel

以此命令开头的代码块被标识为并行区,一般并行区都要以该命令开头。

#pragma omp parallel
{
    block;
}

2. #pragma omp for

后面必须跟for循环,将循环分配给不同线程。

#pragma omp parallel
#pragma omp for
    for(...){
        block;
    }

这里也可以将两个宏合并成一个,使得代码更简洁

#pragma omp parallel for
    for(...){
        block;
    }

3. private or shared

OpenMP默认将全局变量,静态变量设置为共享属性,但是可以通过下列命令控制:
shared(varname, …) : 设置变量varname为共享
private(varname, …) : 设置变量varname为线程私有

#pragma omp parallel for private(x, y)
#pragma omp parallel for shared(sum)

4. critical

设置临界区, 使得同一时刻只能有一个线程访问函数。

#pragma omp critical
    sum += a[i] +b[i]
return sum

也可以给这个命令添加参数,将同一函数放在不同临界区中,使同一时刻可以有多个线程访问。

#pragma omp critical(R1_lock)
    consum(B, &R1)
#pragma omp critical(R2_lock)
    consum(A, &R2)

5. reduction

归约操作,reduction(op:list),op表示一个操作,list代表执行op操作的变量列表。
每个线程会各自拥有一个私有化的list中的变量,当所有线程计算完成后,对各个线程的私有化list进行op操作。

#pragma omp parallel for reduction(+:sum)
    for(i=0; i<n; i++){
        sum += a[i] + b[i];
    }

6. schedule

这个命令用于控制工作量的划分与调度。

//静态划分,将for循环的每chunk次循环划分为一个块,依次将一块分配给一个线程
schedule(static[,chunk])

//动态划分,将chunk次循环划分成一个块,但工作块被放入队列,线程完成一块后从队列中
//取下一个新块,即线程执行哪一块是不确定的。
schedule(dynamic[,chunk])

//动态划分的改进,但分块的大小是不固定的,一开始块比较大,随着工作量的减少,
//块的大小也随之减小,chunk规定块的最小值
schedule(guided[,chunk])

7. section

可以定义多个section,然后让这些section并行地执行,每个section的工作之间应该是相互独立、没有依赖关系的,否则并行的效果不好。

#pragma omp parallel sections
{
    #pragma omp section
    block(1);
    #pragma omp section
    block(2);
    #pragma omp section
    block(3);
}

8. single

这个命令后面的代码只执行一次,即只有一个线程会去执行它。

#pragma omp parallel
{
    DoManyThings();
    #pragma omp single
    {
        ExchangeBounderies();
    }
    DoManyMoreThings();
}

9. master

master与single的作用类似,但不同之处在于master不会阻碍其他线程的继续运行,即其他线程如果遇到#pragma omp master会直接跳过去执行后面的语句。

#pragma omp parallel
{
    DoManyThings();
    #pragma omp master
    {
        ExchangeBounderies();
    }
    DoManyMoreThings();
}

10. nowait

parallel, for, single, sections等都包含隐藏的障碍,即当一个线程完成了并发区的工作之后,为了保证同步,必须等到其他所有线程都结束才能继续运行。
但是如果后面的代码对并行区是没有依赖的,那么这样的等待就是在浪费时间,这时就可以用nowait来去除障碍。

#pragma omp parallel for nowait
for(...){
    ...
}

11. barrier

设置一个阻塞,所有线程都执行到该行后,所有线程才能继续执行后面的代码。

#pragma omp barrier

4. API函数集

头文件

Faiss已经添加了对OpenMP的支持,因此只要在代码中添加头文件omp.h即可。

#include <omp.h>

omp_get_thread_num

返回当前线程的ID, 0~(N-1),N为并行区总线程数。

int omp_get_thread_num(void);

omp_get_num_threads

返回并行区里的总线程数

int omp_get_num_threads(void)

5. 环境变量

OpenMP中定义一些环境变量,可以通过这些环境变量控制OpenMP程序的行为,常用的环境变量:

OMP_SCHEDULE
用于for循环并行化后的调度,它的值就是循环调度的类型;

OMP_NUM_THREADS
用于设置并行域中的线程数;

OMP_DYNAMIC
通过设定变量值,来确定是否允许动态设定并行域内的线程数;

OMP_NESTED
指出是否可以并行嵌套。

OMP_WAIT_POLICY
设置正在等待的线程的所需策略:ACTIVE 或 PASSIVE。
ACTIVE 线程在等待时会占用处理器时间。PASSIVE 线程不会占用处理器时间,并且可能会放弃处理器或进入休眠状态。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

翔底

您的鼓励将是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值