OPENMP FOR CONSTRUCT GUIDED 调度方式实现原理和源码分析

前言

在本篇文章当中主要给大家介绍在 OpenMP 当中 guided 调度方式的实现原理。这个调度方式其实和 dynamic 调度方式非常相似的,从编译器角度来说基本上是一样的,在本篇文章当中就不介绍一些相关的必备知识了,如果不了解可以先看这篇文章 OpenMP For Construct dynamic 调度方式实现原理和源码分析

GUIDED 调度方式分析

我们使用下面的代码来分析一下 guided 调度的情况下整个程序的执行流程是怎么样的:

#pragma omp parallel for num_threads(t) schedule(guided, size)
for (i = lb; i <= ub; i++)
 body;

编译器会将上面的程序编译成下面的形式:

void subfunction (void *data)
{
 long _s0, _e0;
 while (GOMP_loop_guided_next (&_s0, &_e0))
{
   long _e1 = _e0, i;
   for (i = _s0; i < _e1; i++)
     body;
}
 // GOMP_loop_end_nowait 这个函数的主要作用就是释放数据的内存空间 在后文当中不进行分析
 GOMP_loop_end_nowait ();
}

GOMP_parallel_loop_guided_start (subfunction, NULL, t, lb, ub+1, 1, size);
subfunction (NULL);
// 这个函数在前面的很多文章已经分析过 本文也不在进行分析
GOMP_parallel_end ();

根据上面的代码可以知道,上面的代码当中最主要的两个函数就是 GOMP_parallel_loop_guided_start 和 GOMP_loop_guided_next,现在我们来分析一下他们的源代码:

  • GOMP_parallel_loop_guided_start
void
GOMP_parallel_loop_guided_start (void (*fn) (void *), void *data,
unsigned num_threads, long start, long end,
long incr, long chunk_size)
{
 gomp_parallel_loop_start (fn, data, num_threads, start, end, incr,
   GFS_GUIDED, chunk_size);
}

static void
gomp_parallel_loop_start (void (*fn) (void *), void *data,
 unsigned num_threads, long start, long end,
 long incr, enum gomp_schedule_type sched,
 long chunk_size)
{
 struct gomp_team *team;
// 解析到底启动几个线程执行并行域的代码
 num_threads = gomp_resolve_num_threads (num_threads, 0);
 // 创建线程组
 team = gomp_new_team (num_threads);
 // 对共享数据进行初始化操作
 gomp_loop_init (&team->work_shares[0], start, end, incr, sched, chunk_size);
 // 启动线程组执行函数 fn
 gomp_team_start (fn, data, num_threads, team);
}

在上面的程序当中 GOMP_parallel_loop_guided_start,有 7 个参数,我们接下来仔细解释一下这七个参数的含义:

  • fn,函数指针也就是并行域被编译之后的函数。
  • data,指向共享或者私有的数据,在并行域当中可能会使用外部的一些变量。
  • num_threads,并行域当中指定启动线程的个数。
  • start,for 循环迭代的初始值,比如 for(int i = 0; 这个 start 就是 0 。
  • end,for 循环迭代的最终值,比如 for(int i = 0; i < 100; i++) 这个 end 就是 100 。
  • incr,这个值一般都是 1 或者 -1,如果是 for 循环是从小到达迭代这个值就是 1,反之就是 -1,实际上这个值指的是 for 循环 i 大的增量。
  • chunk_size,这个就是给一个线程划分块的时候一个块的大小,比如 schedule(dynamic, 1),这个 chunk_size 就等于 1 。

事实上上面的代码和 GOMP_parallel_loop_dynamic_start 基本上一模一样,函数参数也一致,唯一的区别就是调度方式的不同,上面的代码和前面的文章 OpenMP For Construct dynamic 调度方式实现原理和源码分析 基本一样因此不再进行详细的分析。

  • GOMP_loop_guided_next,这是整个 guided 调度方式的核心代码(整个过程仍然使用 CAS 进行原子操作,保证并发安全)
static bool
gomp_loop_guided_next (long *istart, long *iend)
{
 bool ret;
 ret = gomp_iter_guided_next (istart, iend);
 return ret;
}

bool
gomp_iter_guided_next (long *pstart, long *pend)
{
 struct gomp_thread *thr = gomp_thread ();
 struct gomp_work_share *ws = thr->ts.work_share;
 struct gomp_team *team = thr->ts.team;
 unsigned long nthreads = team ? team->nthreads : 1;
 long start, end, nend, incr;
 unsigned long chunk_size;  // 下一个分块的起始位置
 start = ws->next;
 // 最终位置 不能够超过这个位置
 end = ws->end;
 incr = ws->incr;
 // chunk_size 是每个线程的分块大小
 chunk_size = ws->chunk_size;

 while (1)
  {
     unsigned long n, q;
     long tmp;
// 如果下一个分块的起始位置等于最终位置 那就说明没有需要继续分块的了 因此返回 false 表示没有分块需要执行了
     if (start == end)
return false;
  // 下面就是整个划分的逻辑 大家可以吧 incr = 1 带入 就能够知道每次线程分得的数据就是当前剩下的数据处以线程的个数
     n = (end - start) / incr;
     q = (n + nthreads - 1) / nthreads;

     if (q < chunk_size)
q = chunk_size;
     if (__builtin_expect (q <= n, 1))
nend = start + q * incr;
     else
nend = end;
  // 进行比较并交换操作 比较 start 和 ws->next 的值,如果相等则将 ws->next 的值变为 nend 并且返回 ws->next 原来的值
     tmp = __sync_val_compare_and_swap (&ws->next, start, nend);
     if (__builtin_expect (tmp == start, 1))
break;

     start = tmp;
  }

 *pstart = start;
 *pend = nend;
 return true;
}

从上面的整个分析过程来看,guided 调度方式之所以每个线程的分块呈现递减趋势,是因为每次执行完一个 chunk size 之后,剩下的总的数据就少了,然后又除以线程数,因此每次得到的 chunk size 都是单调递减的。

总结

在本篇文章当中主要介绍了 OpenMP 当中 guided 调度方式当中数据的划分策略以及具体的实现代码, OpenMP 当中 for 循环的几种调度策略的越代码是非常相似的,只有具体的划分策略的 xxx_next 代码实现不同,因此整体来说是相对比较好阅读的。guided 调度方式主要是用剩下的数据个数除以线程的个数就是线程所得到的 chunk size 的大小,然后更新剩下的数据个数再次除以线程的个数就是下一个线程所得到的 chunk size 大小,如此反复直到划分完成。


更多精彩内容合集可访问项目:https://github.com/Chang-LeHung/CSCore

关注公众号:一无是处的研究僧,了解更多计算机(Java、Python、计算机系统基础、算法与数据结构)知识。

OPENMP FOR CONSTRUCT GUIDED 调度方式实现原理和源码分析的更多相关文章

  1. OpenMP For Construct dynamic 调度方式实现原理和源码分析

    OpenMP For Construct dynamic 调度方式实现原理和源码分析 前言 在本篇文章当中主要给大家介绍 OpenMp for construct 的实现原理,以及与他相关的动态库函数 ...

  2. Java并发编程(七)ConcurrentLinkedQueue的实现原理和源码分析

    相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Java并发编程(三)volatile域 Java并发编程(四)Java内存模型 Java并发编程(五)Concurr ...

  3. Kubernetes Job Controller 原理和源码分析(一)

    概述什么是 JobJob 入门示例Job 的 specPod Template并发问题其他属性 概述 Job 是主要的 Kubernetes 原生 Workload 资源之一,是在 Kubernete ...

  4. Kubernetes Job Controller 原理和源码分析(二)

    概述程序入口Job controller 的创建Controller 对象NewController()podControlEventHandlerJob AddFunc DeleteFuncJob ...

  5. Kubernetes Job Controller 原理和源码分析(三)

    概述Job controller 的启动processNextWorkItem()核心调谐逻辑入口 - syncJob()Pod 数量管理 - manageJob()小结 概述 源码版本:kubern ...

  6. Java1.7 HashMap 实现原理和源码分析

    HashMap 源码分析是面试中常考的一项,下面一篇文章讲得很好,特地转载过来. 本文转自:https://www.cnblogs.com/chengxiao/p/6059914.html 参考博客: ...

  7. 深入ReentrantLock的实现原理和源码分析

    ReentrantLock是Java并发包中提供的一个可重入的互斥锁.ReentrantLock和synchronized在基本用法,行为语义上都是类似的,同样都具有可重入性.只不过相比原生的Sync ...

  8. ☕【Java深层系列】「并发编程系列」让我们一起探索一下CyclicBarrier的技术原理和源码分析

    CyclicBarrier和CountDownLatch CyclicBarrier和CountDownLatch 都位于java.util.concurrent这个包下,其工作原理的核心要点: Cy ...

  9. Express工作原理和源码分析一:创建路由

    Express是一基于Node的一个框架,用来快速创建Web服务的一个工具,为什么要使用Express呢,因为创建Web服务如果从Node开始有很多繁琐的工作要做,而Express为你解放了很多工作, ...

  10. Android AsyncTask运作原理和源码分析

    自10年大量看源码后,很少看了,抽时间把最新的源码看看! public abstract class AsyncTask<Params, Progress, Result> {     p ...

随机推荐

  1. Quartz的使用

    Quartz的使用 可以下载该项目进行测试查看:https://gitee.com/zhou-jiahao/quartz_demoq 1 初始Quartz 如果你的定时任务没有分布式需求,但需要对任务 ...

  2. 基于 MQ 的分布式 Serverless 多租任务处理系统架构演进

    本文作者:史明伟 , 阿里云智能高级技术专家. 1 Serverless 异步任务处理系统诞生和挑战 无论是对于云的开发者,还是尝试业务升级的企业客户,Serverless的三个概念 "极致 ...

  3. vue项目中,alert使用

    methods:{ sendData() { ··· // 中间省略的代码 alert(`发送成功,连接失败的号码有:${failList}`) // failList里存的是后台返回的数据 } }

  4. 记录一次PyQt5内存泄漏的问题解决

    前言 前几天利用python-mpv写了一个播放器,但是跑着跑着发现内存越来越大,经过我反复调试终于解决了这个问题. 解决思路 模块定位 首先我是一个模块一个模块测试的,这样可以尽快缩减出问题的代码范 ...

  5. 解决linux mint内置无线网卡失效问题

    前言 同学安装了linux mint,但是内置的无线网卡失效,只能通过有线网卡连接,经过查询得到不是缺少驱动的问题,是内核不支持 解决办法 sudo apt install linux-generic ...

  6. 【Java SE进阶】Day09 字节流、字符流、I/O操作、属性集

    一.I/O概述 1.输入输出 输入:硬盘-->内存 输出:内存-->内存 2.流 字节流:一个字节等于8位 字符流:一个字符=2个字节 二.字节流 1.概述 以字节的方式读取/传输 可以读 ...

  7. 【大数据面试】ClickHouse:介绍、特点、数据类型、引擎、操作、副本、分片

    1.介绍 开源的列式存储数据库(DBMS),由C++编写,用于在线分析处理查询(OLAP) 可以通过SQL查询实时生成分析数据报告 解释: DBMS:数据库管理系统 常见的列式存储数据库:Hbase. ...

  8. PW4052 是一颗适用于单节锂电池的、具有恒压/恒流充电模式的充电管理 IC

    PW4052 是一颗适用于单节锂电池的.具有恒压/恒流充电模式的充电管理 IC.该芯片采用开关型的工作模式, 能够为单节锂电池提供快速. 高效且简单的充电管理解决方案.PW4052 采用三段式充电管理 ...

  9. Guava LoadingCache本地缓存的正确使用姿势——异步加载

    1. [背景]AB实验SDK耗时过高 同事在使用我写的实验平台sdk之后,吐槽耗时太高,获取实验数据分流耗时达到700ms,严重影响了主业务流程的执行 2. [分析]缓存为何不管用 我记得之前在sdk ...

  10. SQL审核平台Yearning

    1.关于Yearming Yearming是一个Sql审核平台,底层使用Go语言,安装和部署方式也很便捷 项目地址 https://guide.yearning.io/install.html git ...