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. Go语言核心36讲12

    作为Go语言最有特色的数据类型,通道(channel)完全可以与goroutine(也可称为go程)并驾齐驱,共同代表Go语言独有的并发编程模式和编程哲学. Don't communicate by ...

  2. MSP430中断小实验——通过按键改变小灯闪烁频率

    本小实验基于MSP430f5529,不同的型号可能管脚和中断配置有所不同. 实现的功能为: 第一次按下按键后,系统以每 2 秒钟,指示灯暗 1 秒,亮 1 秒的方式闪烁.程序采用默认时钟配置: 第二次 ...

  3. 【RocketMQ】顺序消息实现原理

    全局有序 在RocketMQ中,如果使消息全局有序,可以为Topic设置一个消息队列,使用一个生产者单线程发送数据,消费者端也使用单线程进行消费,从而保证消息的全局有序,但是这种方式效率低,一般不使用 ...

  4. 斐波那契散列算法和hashMap实践

    斐波那契散列和hashMap实践 适合的场景:抽奖(游戏.轮盘.活动促销等等) 如果有不对的地方,欢迎指正! HashMap实现数据散列: 配置项目,引入pom.xml: <dependency ...

  5. docker给已存在的容器添加或修改端口映射

    简述: 这几天研究了一下docker, 发现建立完一个容器后不能增加端口映射了,因为 docker run -p 有 -p 参数,但是 docker start 没有 -p 参数,让我很苦恼,无奈谷歌 ...

  6. 笔试面试--Java基础知识

    一.基本概念 1.Java的优点 纯面向对象 平台无关性,"一次编译,到处运行(JVM上)",跨平台,可移植性 丰富类库:多线程.网络通信.垃圾回收 安全性(数组边界检测.byte ...

  7. 【每日一题】【DFS+存已加的值】2022年2月27日-二叉树根节点到叶子节点的所有路径和

    描述给定一个二叉树的根节点root,该树的节点值都在数字0−9 之间,每一条从根节点到叶子节点的路径都可以用一个数字表示.1.该题路径定义为从树的根结点开始往下一直到叶子结点所经过的结点2.叶子节点是 ...

  8. 【Day02】Spring Cloud组件的使用--Nacos配置中心、sentinel流量控制、服务网关Gateway、RocketMQ、服务调用链路(Sleuth、zipkin)

    今日内容 一.配置中心 1.遗留问题 yml配置,每一次都需要重启项目 需要不重启项目拿到更新的结果 引出:配置中心 选择:Spring Cloud Config组件 / Alibaba的Nacos( ...

  9. docker部署项目

    @ 目录 前言 一.下载安装docker: 1.前提工作 1.1 查看linux版本 1.2 yum包更新到最新 1.3 安装工具包 1.4 设置yum源并更新yum包索引 2.安装docker 2. ...

  10. 持续发烧,聊聊Dart语言的静态编译,能挑战Go不?

    前言 前两天写了几篇文章,谈了谈Dart做后端开发的优势,比如: <Dart开发服务端,我是不是发烧(骚)了?> <持续发烧,试试Dart语言的异步操作,效率提升500%> & ...