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. 10 | Kubernetes一键部署利器:kubeadm

    你好,我是张磊.今天我和你分享的主题是:Kubernetes一键部署利器之kubeadm. 通过前面几篇文章的内容,我其实阐述了这样一个思想:要真正发挥容器技术的实力,你就不能仅仅局限于对Linux容 ...

  2. apktool回编译报错

    报错 error: No resource identifier found for attribute 'XXX' in package 'XXX' 解决 将xml文件中 "http:// ...

  3. MySQL进阶实战1,数据类型与三范式

    一.选择优化的数据类型 MySQL支持的数据类型非常多,选择正确的数据类型对于获得高性能至关重要. 1.更小的 一般情况下,应该尽量使用较小的数据类型,更小的数据类型通常更快,因为占用更少的磁盘.内存 ...

  4. WEB入门——爆破21-28

    WEB21 首先尝试网站后台常见登陆的弱口令,发现错误   则使用burp suite抓包试试看 通过分析,在未填入账号密码时,响应头如下所示: 填入弱口令账号密码,发现响应头如下: 则对应可知账号密 ...

  5. Day29 Linux相关命令的使用

    今日内容 基本概念 安装 基本命令 在linux上安装软件 jdk mysql jdk Nginx的安装 一.概述 1.Unix linux基于Unix,Unix由贝尔实验室在1969年开发 一开始由 ...

  6. Java中的反射机制及反射的优缺点

    1. 反射的概念 反射 机制指的是,程序在运行时能够获取自身的信息.在 java 中只要给定类的名字,就能够获取类的所有属性和方法. 反射是 Java 中很多高级特性的基础,比如 注解.动态代理 以及 ...

  7. <三>function函数对象类型的应用示例

    std::function是一组函数对象包装类的模板,实现了一个泛型的回调机制.function与函数指针比较相似,优点在于它允许用户在目标的实现上拥有更大的弹性,即目标既可以是普通函数,也可以是函数 ...

  8. python基础-常用内置包

      内置包是python自带的一些功能模块,有需求时可以在自己文件中直接导入使用. 1.datetime包   python中的时间包,可以在业务开发中辅助我们处理时间信息: # datetime可以 ...

  9. uniapp微信小程序内部跳转其他微信小程序

    uniapp小程序内点击某个按钮跳转另外一个小程序连接,具体实现步骤如下: <view class="home-Item" @click="goNativeinde ...

  10. python 学生管理系统 文件版 增删改查

    # 在程序开始之前完成数据的读取 # 存在着循环 # 循环的内容 # 将操作内容分解为函数 def sel_student(students_list): """ 查看所 ...