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. 思维分析逻辑 3 DAY

    目录 指标分析 指标选择原则 指标体系建立 步骤 流量分析 渠道分析(从哪来) 常见渠道及渠道分类 渠道推广过程 渠道的指标 渠道分析方法 转化及价值分析(经过什么?产生什么价值?) 漏斗分析 功能模 ...

  2. 【Docker】容器使用规范--安全挂载建议

    容器挂载过程和安全挂载建议 绑定挂载 本文所提到的挂载主要指绑定挂载(bind mount),即通过-v /xx/xx:/xx/xx 和 --mount type=bind,xxx,xxx两种方式设置 ...

  3. 图解S.O.L.I.D原则

    如果您熟悉面向对象的编程,那么您可能已经听说过SOLID原理. 这五项软件开发原则是构建软件时要遵循的准则,以便于扩展和维护. 它们受到软件工程师Robert C. Martin的欢迎. 在线上有很多 ...

  4. HDLBits答案——Getting started

    Getting started 1 Step one module top_module( output one ); // Insert your code here assign one = 1' ...

  5. 学习ASP.NET Core Blazor编程系列十四——修改

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...

  6. tostring、(string)和 String.valueOf()

    上周遇到一个问题,只怪自己平时没注意这个细节,从数据库取数据在map集合里,取出该值是我用了.tostring的方法,一次在当取出数据为空时代码报java.lang.NullPointerExcept ...

  7. Hadoop安装-分布式-Fully

    Hadoop安装-分布式-Fully 〇.所需资料 一.配置 1.基础配置 (1)系统安装 (2)hostname主机名配置 (3)ip地址.dns.hosts映射文件配置 (4)关闭防火墙与seli ...

  8. JavaEE Day06 JDBC连接池&JDBCTemplate

    今日内容: 数据库连接池 简化操作--Spring JDBC提供的 JDBC Template(JDBC的封装) 一.数据库连接池 1.引入 之前:每一次都要获取连接.释放连接-- 现在:连接重复使用 ...

  9. 【Shell案例】【打印指定行用sed、for循环、head和tail配合使用】4、输出第5行的内容

    描述写一个 bash脚本以输出一个文本文件 nowcoder.txt 中第5行的内容. 示例:假设 nowcoder.txt 内容如下:welcometonowcoderthisisshellcode ...

  10. 说说真实Java项目的开发流程,以及面试前的项目准备说辞

    介绍项目是必不可少的Java面试环节,求职者需要借此证明自己真实Java项目的经验,如果再做的好的话,需要借此展开自己的亮点说辞. 不过之前如果只有学习项目经验,比如是自己跑通一个项目,或者是在培训班 ...