您好,欢迎来到聚文网。 登录 免费注册
OPENACC高性能并行编程:概念与策略

OPENACC高性能并行编程:概念与策略

  • 字数: null千字
  • 装帧: 平装
  • 出版社: 机械工业出版社
  • 作者: [美] 苏妮塔·钱德拉塞克兰(Sunita Chandrasekaran) [德]吉
  • 出版日期: 2019-04-01
  • 商品条码: 9787111623236
  • 版次: 1
  • 开本: 16开
  • 页数: 244
  • 出版年份: 2019
定价:¥79 销售价:登录后查看价格  ¥{{selectedSku?.salePrice}} 
库存: {{selectedSku?.stock}} 库存充足
{{item.title}}:
{{its.name}}
精选
内容简介
本书是介绍大规模并行编程OpenACC的综合实践性书籍之一。书中前3章介绍了OpenACC背后的概念和OpenACC开发工具;第4章至第7章带你了解个真实世界的OpenACC程序,并揭示OpenACC程序编译背后的魔力,从而引入更多概念;第8章至0章涵盖不错主题,例如OpenACC的替代方案、底层设备交互、多设备编程和任务并行性;1章和2章探讨了OpenACC实现潜在新语言特性的各种研究领域。
作者简介
闫林,北京航空航天大学软件工程硕士。中兴通讯IT技术学院副院长、中科院大学兼职教授、国家科学技术奖励评审专家、中国电子学会两化融合专家委员、中国互联网协会青年专家、中国信息通信研究院安全评估中心不错评估专家、中国电子学会重量专业技术人员继续教育基地专家库入选专家。研究包括OpenACC等相关IT技术,出版13本IT专著。
目录
赞誉推荐序译者序前言致谢贡献者简介译者简介章  OpenACC概述 11.1  OpenACC语法 21.1.1  导语 31.1.2  子语 31.1.3  API例程与环境变量 41.2  计算构件 41.2.1  kernels 51.2.2  parallel 61.2.3  loop 71.2.4  routine 71.3  数据环境 91.3.1  数据导语 91.3.2  数据子语 101.3.3  cache导语 111.3.4  部分数据传输 111.4  总结 121.5  练习 12第2章  循环级并行性 142.1  kernels循环与parallel循环的比较 152.2  并行性的三个级别 182.2.1  gang、worker与vector子语 182.2.2  将并行性映射到硬件 192.3  其他loop构件 202.3.1  循环折叠 202.3.2  independent子语 212.3.3  seq与auto子语 222.3.4  reduction子语 232.4  总结 252.5  练习 26第3章  OpenACC编程工具 273.1  架构的通用特性 273.2  编译OpenACC代码 283.3  OpenACC应用程序的性能分析 303.3.1  性能分析层次和术语 303.3.2  性能数据获取 313.3.3  性能数据记录和显示 323.3.4  OpenACC性能分析接口 323.3.5  支持OpenACC的性能工具 333.3.6  NVIDIA性能分析工具 343.3.7  针对混合应用程序的Score-P工具基础架构 353.3.8  TAU性能系统 403.4  识别OpenACC程序中的bug 423.5  总结 443.6  练习 45第4章  使用OpenACC编写个程序 484.1  案例研究 484.1.1  串行代码 494.1.2  编译代码 554.2  创建一个原生的并行版本 564.2.1  找到热点 564.2.2  使用kernels安全吗 564.2.3  OpenACC实现 564.3  OpenACC程序的性能 594.4  优化的并行版本 604.4.1  减少数据移动 614.4.2  特别聪明的小改动 624.4.3  最终的结果 634.5  总结 654.6  练习 66第5章  编译OpenACC 675.1  并行性的挑战 685.1.1  并行硬件 685.1.2  映射循环 695.1.3  内存层次结构 715.1.4  归约 725.1.5  应对并行性的OpenACC 725.2  重建编译器 735.2.1  编译器可以做什么 745.2.2  编译器不能做什么 755.3  编译OpenACC 765.3.1  代码预备工作 775.3.2  调度 775.3.3  串行代码 785.3.4  用户错误 795.4  总结 805.5  练习 81第6章  很好编程实践 836.1  通用准则 846.1.1  优选化设备计算 846.1.2  优化数据局部性 856.2  优选化设备计算 866.2.1  原子操作 866.2.2  kernels构件与parallel构件 876.2.3  运行时调优和if子语 886.3  优化数据局部性 896.3.1  最少化数据传输 896.3.2  数据复用和present子语 906.3.3  非结构化数据生命周期 916.3.4  指定数组形状 926.4  典型示例 926.4.1  背景知识:热力学报表 926.4.2  基线CPU版本的实现 936.4.3  性能分析 936.4.4  使用OpenACC进行加速 946.4.5  优化数据局部性 966.4.6  性能研究 976.5  总结 986.6  练习 98第7章  OpenACC与性能可移植性 997.1  挑战 997.2  目标架构 1007.2.1  特定平台的编译 1017.2.2  x86_64多核与NVIDIA 1017.3  OpenACC性能可移植性 1017.3.1  OpenACC内存模型 1027.3.2  内存架构 1027.3.3  代码生成 1027.3.4  性能可移植性的数据布局 1037.4  代码重构以实现性能可移植性 1037.4.1  HACCmk 1037.4.2  面向多种架构 1057.4.3  openACC在NVIDIA K20x GPU上的应用 1067.4.4  openACC在AMD Bulldozer多核上的应用 1077.5  总结 1087.6  练习 109第8章  并行编程的其他方式 1118.1  编程模型 1118.1.1  OpenACC 1138.1.2  OpenMP 1138.1.3  CUDA 1148.1.4  OpenCL 1148.1.5  C++ AMP 1158.1.6  Kokkos 1158.1.7  RAJA 1168.1.8  线程构建模块 1168.1.9  C++17 1168.1.10  Fortran 2008 1178.2  编程模型组件 1178.2.1  并行循环 1188.2.2  并行归约 1198.2.3  紧密嵌套循环 1218.2.4  分层并行性(非紧密嵌套循环) 1228.2.5  任务并行性 1248.2.6  数据分配 1258.2.7  数据传输 1268.3  案例研究 1278.3.1  串行实现 1288.3.2  OpenACC实现 1298.3.3  OpenMP实现 1308.3.4  CUDA实现 1318.3.5  Kokkos实现 1348.3.6  TBB实现 1368.3.7  一些性能数字 1388.4  总结 1408.5  练习 140第9章  OpenACC与互操作性 1429.1  在OpenACC中调用原生设备代码 1429.1.1  示例:使用DFT进行图像滤波 1439.1.2  host_data导语及use_device子语 1459.1.3  目标平台相关API例程 1479.2  在原生设备代码中调用OpenACC 1499.3  OpenACC互操作性不错话题 1499.3.1  acc_map_data 1499.3.2  在OpenACC kernel中调用CUDA设备例程 1519.4  总结 1529.5  练习 1520章  OpenACC不错特性 15310.1  异步操作 15310.1.1  OpenACC异步编程 15510.1.2  软件流水线 16010.2  多设备编程 16810.2.1  多设备流水线 16910.2.2  OpenACC与MPI 17210.3  总结 17610.4  练习 1761章  使用OpenACC的创新研究思路,部分 17711.1  神威OpenACC 17711.1.1  SW26010众核处理器 17811.1.2  神威太湖之光中的内存模型 17811.1.3  执行模型 18011.1.4  数据管理 18111.1.5  总结 18311.2  针对加速器的嵌套循环编译器转换 18411.2.1  OpenUH编译器基础架构 18511.2.2  循环调度转换 18711.2.3  循环调度的性能评估 19011.2.4  OpenUH的其他研究课题 1932章  使用OpenACC的创新研究思路,第2部分 19412.1  一个基于导语的高性能可重构计算框架 19412.1.1  介绍 19512.1.2  OpenACC到FPGA的基线翻译 19612.1.3  用于高效FPGA编程的OpenACC扩展和优化 19812.1.4  评估 20312.1.5  总结 20712.2  使用XcalableACC编程加速集群 20712.2.1  XcalableMP介绍 20812.2.2  XcalableACC:当XcalableMP遇上OpenACC 21112.2.3  Omni编译器的实现 21312.2.4  在HA-PACS上的性能评估 21512.2.5  总结 220
摘要
    章OpenACC概述James Beyer,NVIDIASunita Chandrasekaran,特拉华大学Guido Juckeland,HZDROpenACC是用于C/C++和Fortran的基于导语(directive)的不错编程模型。如果要在异构高性能计算(HPC)硬件架构上进行编程,使用OpenACC的编程工作量比低级模型要少得多。OpenACC编程模型基于程序员在其C/C++或Fortran程序中,嵌入如何实现代码并行化的提示。编译器在编译时在指定的硬件平台上运行代码。通过这种方式,编译器可以处理大部分翻译的复杂细节,而不会增加应用程序科学家或程序员的负担。这使得科学家们可以专注于他们的领域知识,而不是目标架构的复杂细节。因为OpenACC是一种基于导语的模型,可以通过提示来扩充给定的代码库,所以可以简单地以串行方式编译代码,即使忽略导语也仍然能产生正确的结果。因此,你可以维护或保留单个代码库,同时提供跨多个平台的可移植性。目前,OpenACC生产型编译器可以支持的硬件平台有:传统的基于X86的硬件平台、多核(multicore)平台、图形处理单元(GPU)等加速器、OpenPOWER处理器、Knights Landing(KNL)和ARM(Advanced RISC Machines)处理器。OpenACC研究型编译器也可以用于现场可编程门阵列(FPGA)。已经有针对Intel Xeon Phi和ARM处理器的开发工作的公开报道。异构计算的趋势将会继续下去。次百亿亿级(pre-exascale)机器正在为百亿亿级(exascale)计算机系统(从2020年到2023年)构建舞台。这些机器中的大多数具有由混合的处理单元类型组成的异构节点。这种趋势很可能会延续到具有百万个节点的百亿亿级架构,每个节点有多达上千个核,从而具有大约十亿个核的计算能力。百亿亿级计算面临的很关键挑战是高功耗,具有高能效加速器的这些机器似乎是能够解决这一挑战的关键设备之一。随着硬件的创新,软件不得不升级,以利用大量的节点并发性(on-node concurrency),并可以处理未来多代HPC硬件。本章介绍了OpenACC编程模型。不管目标平台如何,我们都会解释它的一些基本特性:如何构造OpenACC导语语法。如何使用parallel构件和kernels构件来加速程序的计算密集型部分。如何利用loop构件来表达循环级并行性(parallelism)。如何使用数据环境子语在传统的核和加速器之间迁移数据。1.1  OpenACC语法OpenACC是一种语言吗?就它自身而言,不是。根据OpenACC规范,OpenACC是编译器导语、库例程和环境变量的集合。但是,当应用编程接口(API)与支持的基本语言(C、C++或Fortran)中的一种结合使用时,便诞生了一种新的编程语言。这种新语言允许表示代码中固有的并行性,以便编译器可以将其翻译为适合各种并行架构的形式。OpenACC使用所谓的导语来标注程序的源代码。支持OpenACC的编译器可以解释这些导语,并根据导语的说明生成并行的可执行代码。如果使用不支持OpenACC的编译器编译代码,或用OpenACC解释,编译器会忽略这些导语并生成没有任何并行性的串行程序。OpenACC导语总是按照以下方式设置(对于C/C++语言):在Fortran语言中:编译器导语的关键字(C/C++的#pragma或Fortran的!$)后跟导语类型,即OpenACC导语的acc。接下来是实际导语,告诉编译器该做什么,然后是一个或多个可选的子语以提供进一步的信息。通过在前一行的末尾放置反斜杠(\),就可以在下一行继续带导语的行。这样的一个OpenACC导语,其后紧跟语句、循环或结构化代码块。它们一起形成了一个所谓的OpenACC构件(OpenACC construct)。1.1.1  导语acc标签后面的个单词称为导语。导语(directive)是一个“?指令?”,告诉编译器对它后面的代码块如何操作。OpenACC有三种不同类型的导语。1.计算导语(compute directive)。它们给一个代码块打上标记,可以利用固有数据并行性对这段代码进行加速,并将(代码要执行的)工作分配到多个线程。OpenACC计算导语是parallel、kernels、routine和loop。2.数据管理导语(data management directive)。OpenACC程序的一个关键优化是避免内存位置之间不必要的数据迁移。仅使用计算导语会导致数据的这种移动,因为编译器必须保持并确保一种状态,这种状态等同于串行执行。该类型导语可以指定加速器上的数据生命周期从而改写这些(数据的)默认值,以此来扩展计算构件的能力。此外,还可以指定某种数据访问处理的类型。OpenACC数据导语有data、update、cache、atomic、declare、enter data和exit data。enter data和exit data是当前专享由两个单词组成的OpenACC导语。3.同步导语(synchronization directive)。OpenACC还支持一些任务的并行,允许同时执行多个构件。要显式等待一个或所有并发任务,请使用wait导语。1.1.2  子语每个OpenACC导语都可以通过一个或多个子语来扩充。子语增加了额外的信息,告诉编译器用OpenACC构件需要做什么事情。不是所有的子语都可以与所有的导语进行组合,请参见本书其余部分的详细解释。大多数子语接受括号中的附加参数。一般来说,子语分为三类。1.数据处理(data handling)。这些子语为一些指定变量分配特定的行为,从而修改编译器对这些指定变量的分析。相关子语有default、private、f?irstprivate、copy、copyin、copyout、create、delete和deviceptr。2.工作分配(work distribution)。编译器在生成的线程中分配工作时,由编译器选定值,这些子语能重写这些编译器选定值。相关子语有seq、auto、gang、worker、vector、tile、num_gangs、num _workers和vector_length。3.控制流程(control f?low)。这些子语允许在程序运行时对并行执行进行指导。(并行)执行可以被标记为基于条件的(if或if_present)、依赖重写的(independent和reduction)以及对任务并行有特别要求的(async和wait)。1.1.3  API例程与环境变量OpenACC还提供对程序执行更精细、更低级别的控制。为此,需要在OpenACC运行时使用API,编译器在翻译导语时也使用该API。为了使API可用,可以在此代码中包含OpenACC头文件(对于C/C++语言):在Fortran语言中为:使用OpenACC API例程会导致对OpenACC运行环境的依赖。如果程序现在使用不支持OpenACC的编译器进行编译,由于该编译器中缺少运行时的环境,编译和链接将失败。现在程序可以直接使用OpenACC运行时的许多函数。这些例程用于以下情况。设备管理:查询和设置使用的计算设备类型和编号。也可以通过环境变量ACC_DEVICE_TYPE和ACC_DEVICE_NUM来设置。OpenACC运行时的初始化和关闭。任务并行性:测试并等待异步启动的工作。内存管理:手动分配、映射和释放计算设备上的内存以及手动数据传输。1.2  计算构件因为计算构件在并行线程中分配工作,以此获得更好的性能并缩短程序执行时间,所以每个OpenACC程序的“?面包?”和“?黄油?”是计算构件。OpenACC在线程上分发循环,目的是为一个线程分发一个循环迭代。为了做到这一点,划分了两个通用的分发导语:kernels和parallel。两者都可以使用loop导语进行扩充,以便把某些类型的工作分发标记为独立的循环,或为这些循环分配附加的子语。1.2.1  kernelskernels导语是OpenACC提供的两个计算构件中的个。这两个构件都用于将代码卸载到计算设备。然而,这两个导语的理念是不同的。kernels构件仅仅作为一个警讯(sentinel)来告诉编译器,如果可能的话,这个代码区域应该放置在加速器上。但是,当kernels构件中出现循环时,事情会变得很有趣。由于kernels构件让编译器去执行繁重的代码并行化工作,因此对编译器的期望是:要么顺序执行代码,要么为了利用并行性将循环嵌套并行化。无论编译器怎样选择,都必须确保正确的、顺序一致的计算结果。可以使用几个子语来修改kernels构件,但是因为kernels构件旨在成为一个编译器驱动的机制,所以我们在下一节中讨论这些子语。然而,有一种情况值得研究:kernels构件中的多重嵌套循环。在这段代码中,编译器可以自由选择做至少两件事情。件事情,它可以决定将两个循环嵌套融合成一个循环嵌套:这种融合可能允许更高效地利用可用资源,因为它不会带来启动两个kernel的开销。第二件事情是,编译器可以选择生成两个kernel,这两个kernel可以编码为两个parallel区域。1.2.2  parallelparallel导语是OpenACC提供的第二个计算构件。尽管kernels导语将责任放在编译器上以生成正确的代码,但parallel导语将责任放在用户的头上以阐明并行化的内容,并确保生成正确的代码。parallel导语采取的立场是,用户很好了解正在编写的代码的并行特性,并可以正确表达该区域中的所有并行性。现在看一下前面的例子,将其单纯地改为只使用parallel构件。这段代码有什么问题呢?它正在使用一些并行方式运行循环嵌套,但存在冗余。稍后我们将修复冗余的执行,但重点关注的是,编译器可以自由选择执行此循环的线程数。这意味着代码可能运行缓慢,且可能给出正确的结果,也可能给出错误的结果。还请注意,编译器将检测到parallel区域内使用的对象a和b,并产生数据迁移,这些迁移可能在将对象移动到设备时用得到。由于编译器可以使用任意数目的并行线程随意运行这段代码,因此它不具有可移植性。因为某些实现会选择采用一个线程并给出正确答案,而其他实现可能会选择其他方式并给出错误答案。1.2.3  looploop构件可以在parallel构件和kernels构件中使用。在parallel构件内部,loop构件告诉编译器该循环的迭代是独立的。循环的迭代不会修改其他迭代使用的数据,因此所有迭代都能以并行方式执行。在kernels构件内部,如果编译器在编译时无法确定循环的独立性,可以使用loop构件告诉编译器该循环是独立的。在这两个计算构件中,可以使用seq子语来告诉编译器以顺序方式执行该循环。foo和bar中的代码是在OpenACC中表达类似事物的两种不同方式。原因在于如果编译器在其他地方看不到更好的用法,那么编译器总是可以随心所欲地在循环上使用更多的并行性级别。foo ()中的代码允许编译器选择其希望利用的并行性级别。bar ()中的代码告诉编译器它应该利用并行性的所有线程级别,如第2章所述。1.2.4  routine在本节中,除了一种例外情况,函数或子例程(subroutine)仅出现在计算构件之外。这是故意设计的,因为在计算构件中的调用比仅调用一个简单函数更复杂。由于函数可以在具有任意并行的计算构件中被调用,因此必须针对这种情况进行编译。这样,我们使用routine导语将函数标记为可能的并行。routine导语用于两个地方:过程定义和过程声明。此代码示例显示了可以使用routine导语修改函数foo的三种方式。种用法是将导语放在该函数的声明上,并告诉编译器在计算构件中对此函数的任何调用都会采用并行机制。第二种使用导语的命名形式,声明一个已经原型化的函数使其拥有设备版代码,同个示例。routine导语的第三种用法是告诉编译器生成该函数的设备版本,以便计算构件可以调用该过程。routine导语与并行性的三个级别(gang、worker、vector)的结合很复杂,这将在第2章中介绍。为简单起见,可以这样说,并行性的任意一个级别很多只可以被请求一次,特别是对于嵌套的例程和循环。routine导语还使用了这里介绍的另外三个子语。这段代码中的个导语告诉编译器,在为NVIDIA目标设备编译时,只要看到在计算构件中调用foo(),就应该用foo_nvidia替换调用名称。该导语还告诉编译器,除NVIDIA以外的所有目标设备,函数foo()将只利用vector级并行;然而,在NVIDIA的目标设备中,函数foo_nvidia将同时使用gang级和vector级并行,而不在函数外部提供并行。第二个导语告诉编译器为设备生成代码,而不为主机生成代码。至少有两个理由说明这么做是有用的。首先,它减少了生成的代码的文本大小。其次,它确保不能被主机编译的代码(即特定设备的内部代码)不会提供给主机编译器。现在,虽然这看起来很好复杂,但请放心,这也是很好不错的OpenACC程序的优选复杂级别了,并且在许多情况下不是必需的。1.3  数据环境OpenACC用于处理这样的环境:在与主程序不在同一个存储空间的设备上执行计算构件,因此需要在主机和设备之间进行数据迁移。在本节中,我们将讨论通过由OpenACC提供的全套导语和子语,怎样控制内存布局和移动。但是,首先来看看在规范中说明的会自动完成的部分。以下变量被预先设定为private,这意味着每个执行元素将创建一个副本,具体取决于被使用的并行类型:1.与loop构件相关的循环控制变量。2.在parallel或kernels区域中包含的Fortran do循环索引变量。3.在C代码块内声明的变量。4.在过程内部声明的变量。在parallel区域中使用但未在数据子语中列出的标量变量,被预先定义为f?irstprivate(使用构件之前的值进行初始化的私有变量)或private,具体取决于对象在区域中是先读取还是先写入。规范定义了与数据对象有关的两个重要概念:数据区域(data region)和数据生命周期(data lifetime)。数据区域是一个结构化块的动态范围,这个结构化块与隐式或显式数据构件相关联。数据生命周期从对象抢先发售在设备上可用时开始,到对象在设备上不再可用时结束。一共定义了四种类型的数据区域:数据构件、计算构件、过程和整个程序。1.3.1  数据导语有两种类型的数据导语:结构化和非结构化。在数据生命周期开始和结束时,结构化数据区域(structured data region)通过单个词法范围来定义。a的数据生命周期始于左大括号,并在右大括号处结束。下面是Fortran中的代码。非结构化数据区域(unstructured data region)通过“?开始-结束对?”(begin-end pair)来定义边界,“?开始-结束对可以不在相同的词法范围内。正确放置数据导语可以大大提高程序的性能。尽管如此,正因为可以通过添加数据子语(这些子语与可用于结构化数据构件的子语相同)的方式,在parallel构件和kernels构件中处理所有数据迁移,因此放置数据导语应该被认为是一种优化。1.3.2  数据子语有6个数据子语可以用在计算构件和数据构件,以及一个仅用来退出数据构件的数据子语。数据子语(data clause)为命名的变量和数组指定了特定的数据处理方式。所有分配数据的子语的基础都是create子语。此子语为设备上的子语中列出的对象创建一个数据生命周期。这意味着在非共享内存设备上,在与构件关联期间,该对象的内存将是可用的。“?可用?”表示如果已经在设备上为该对象创建了内存,则运行时当设备上不再需要对象内存的时候,只需更新引用计数。这些引用计数用于跟踪内存使用情况。如果对象尚未被分配内存,则运行时将为该对象分配内存,并在使用时更新引用计数。所以我们知道设备上的数据是被运行时跟踪的,但为什么要将这些数据公开给用户呢?接下来进入present子语。它是每个数据子语的部分。但与其说是数据子语,不如说是簿记(bookkeeping)子语。它不确保能让一个对象在设备上变得可用,而是确保该对象在设备上一定可用。为什么这样会很有用?库编写者可能希望确保对象已经在设备上,以便可以将计算发送到设备(如果需要数据移动,则通常是不利的)上。接下来的三个子语在名称和功能上都有关系。我们从很复杂的子语开始:copy。本子语从present子语开始。如果数据不存在,则执行create子语。一旦对象在设备上,copy子语在区域的开头将数据从主机复制到设备。当区域结束时,如果对象的引用计数(与对象有关联的数据构件的数量)即将变为零,则将数据复制回主机并释放内存。copyin子语执行copy子语在进入数据区域过程中的所有动作,但是在区域结束时数据不会被复制回主机。只有引用计数为零时才会释放数据存储。copyout子语执行copy子语的所有动作,不同的是copyout不会在数据区域的入口处将数据复制到设备。delete子语功能强大。然而,与许多其他功能强大的事物一样,它也很危险。delete子语做两件事。首先它确定对象是否存在,如果不存在,则什么也不做;其次,它通过强制引用计数为零并释放内存,以便从设备的数据环境中删除对象。deviceptr子语用来加强对使用设备的本地库和调用的支持。它告诉OpenACC系统,该子语中的指针包含驻留在设备上的地址。这允许你控制OpenACC之外的数据放置,但仍然可以通过API使用这些数据。1.3.3  cache导语cache导语提供了一种描述数据的机制,如果可能的话,这些数据应该能被迁移到更快的内存。在这个例子中,缓存告诉编译器,for循环的每次迭代只使用数组b中的一个元素。对编译器来说,这提供了足够的信息来确定这个循环是否将由n个线程运行。编译器应该将数组中的n个元素移动到更快的内存中,并在这个更快的内存副本中工作。这种移动并不是没有代价的,但是如果循环体对被移动到快速内存的对象做到了充分重用,则其开销可能是很好值得的。如果缓存导语中的对象是只读的,则开销会大大降低。但是,编译器必须在循环进入或退出时执行数据分析,以确定该对象是否存在。

蜀ICP备2024047804号

Copyright 版权所有 © jvwen.com 聚文网