刘加伟,郭 强,庄 园,张海红,王 利,曾云辉+
(1.齐鲁工业大学(山东省科学院) 计算机科学与技术学部,山东 济南 250014;2.齐鲁工业大学(山东省科学院) 山东省计算中心(国家超级计算济南中心),山东 济南 250103)
国产神威系列超级计算机系统支持多种常见的编程模型和并行标准,常用的有消息传递接口(message passing interface,MPI)、异构系统并行编程模型(open accelerators,OpenACC)、加速线程库(acceleration thread library)等。当前程序员在神威超级计算机上进行众核化加速时会面临编程模型选择的问题。虽然OpenACC执行方便,但是并行粒度和并行化效率不高。OpenACC通过导语及子语就可以隐式的实现并行编程[1,2],即使是针对神威异构众核处理器的结构特点而改进的OpenACC*[1]也不能对核组内的线程进行灵活的控制和调度,程序优化的思想也大多是设法提高数据的传输效率,在不追求极致的计算效率的情况下采用OpenACC*并行实现;而Athread性能更优,而且编写灵活,能够更好地发挥出核组内多从核并行的加速性能[3],但相较于OpenACC*编写代码的工作量更大,编程人员在众核化编程中容易出现书写失误,进而需要对代码进行人工排查,这将导致众核化工作效率降低。基于这个问题,亟需要借助自动化的技术辅助众核代码的编写。
目前国内外已有很多研究人员在进行关于自动生成并行代码技术和方法的研究[4,5]。付昊桓等实现了OpenACC代码到基于神威众核处理器下Athread代码,主要依靠源代码到源代码的翻译工具来实现自动化和高效的移植[4];朱效民等第一次尝试在SW26010上进行代码自动生成研究,设计实现了二维Fortran代码转换为Athread代码的自动代码生成器。该工具使用源到源的转换方法,通过输入Fortran代码的数组维度信息、循环索引、计算和循环结束关键词来生成从核代码。该工具仅仅是对二维Fortran代码进行自动转换,缺少对其它维度以及使用C语言编写的源程序支持[5]。上述研究主要依赖编译器来实现程序的源到源的自动翻译,这种编译器实现方法后续修改比较困难,难以完善;而本文设计的转换工具基于模板程序进行转换,易于修改,而且可以及时利用新的高效从核优化方法,便利性强[6-8]。
本文的主要工作是,提出了一套结构清晰易用的Athread众核程序模板框架,设计提出一种可以使源程序自动转换为Athread方式的方法,研发实现了一个面向神威异构众核处理器上Fortran/C语言核心代码段基于Athread方式的众核代码转换系统,旨在提高开发人员的众核化工作效率。
基于目前人工编写Athread方式众核代码的现状和技术方法,我们泛化提出了一套主程序调用master程序再由master程序调用slave程序的三层模板程序框架,设计研发了一个面向C语言和Fortran语言使用的众核代码自动转换工具。该工具根据上传的制导文件和源文件,选择对应的转换模板转换生成Athread方式的众核代码,转换成功后可以对生成的文件进行编辑保存和下载。
众核代码自动生成工具可以使用可执行文件进行单机版的操作,也可使用Web版进行使用。Web版采系统用B/S模式,用户通过浏览器可对该工具进行操作。服务器采用Nginx,它具有简单的负载均衡和容错,后台的数据服务采用MySQL数据库,主要包括用户权限信息和资源信息,其中资源信息包括上传的源代码和转换后生成的一系列Athread代码,对提交的文件做统一管理[9]。该工具在设计上充分发挥了各种语言的优点,文件读写采用异步的方式实现,体系结构如图1所示。
将源文件和制导文件上传到众核代码转换平台之后,在Web页面上进行操作,选择对应的模板进行转换,选择模板的意义在于选择handlebars渲染文件路径。转换之后两个文件的字符串直接通过Web页面调到Java后台,在后台接收到文件之后,将制导文件内容、源文件内容和模板路径交给核心库,由核心库统一处理,核心库通过模板路径获取到所需要的模板文件,对文件进行语法词法解析和渲染,输出的文件保存在硬盘上并把路径返回给Java后台,Java后台再把路径发送到前台。
随着神威系列超级计算机系统的发展,尽管系统架构发生改变,但是“由主程序调用master.c程序,再由master.c程序调用slave.c程序”的编程模式依然是主流思想[1]。本工具以主程序、master.c程序和slave.c程序作为模板,基于制导文件进行转换,还添加了一个全局变量文件定义相关参数,实现对不同平台的扩展。用户使用本工具时,只需要编写制导文件即可。
1.2.1 主程序
在主程序中,除过实现核心段的众核化之外,一个常见需求就是计算结果比对和运行计时。通过计时函数来获取主程序核心段计算时间和从核计算时间,按制导文件自动对原主核核心代码段和众核调用代码段进行计时插桩,比较耗时情况,并对加速效果进行输出。目前采用以基于getlooptime()为基础的计时方法。主程序以逐元素比较原主核计算变量的结果与众核计算变量的结果的方式来判断超出精度范围的位置和数值,并进行输出。在主程序模板中还添加了条件编译语句,用于选择程序是众核化运行、主核运行还是进行比对。
1.2.2 master.c
在master.c程序模板中,将常用的数据参数以结构体的形式存储,结构体中成员类型包含了常见集中数据类型,每种类型的大小分别受不同的参数控制。在从核启动的时候将结构体加载到每个从核的LDM(local data memory)中,减少从核直接访主存的次数,实现数据的重复调用,提高程序性能。此外,为了提高二维数组的可扩展性,该模板采用spawn方式[10],以便对多个并行的二维数组计算的核心段进行从核分区并行。
1.2.3 slave.c
为避免从核使用动态分配函数出现的不稳定现象,slave.c从核程序模板采用静态存储变量的方法,即采用LDM静态空间分别开设一个较大的整型和浮点型的静态数组,对各个变量按结构体参数分别对应进行自动映射。此外,为了减少用户编程的工作量,提高开发效率,我们使用了新一代神威众核架构下实现的从核间通信的接口函数[11]。
1.2.4 制导文件
基于前述模板,转换工具按照用户提供的制导文件对原始程序代码(filename.f90/.c)实现众核化,自动生成filename_struct.h头文件、filename_sw.f90/.c新的主程序、filename_master.c和filename_slave.c文件。
在使用转换工具前,需要用户针对原始代码程序filename.f90/.c中拟转换的核心代码段编写一个制导文件,包括如下内容。
(1)明确拟众核化的代码段范围,按照起始行号表述。
(2)按照输入变量、输出变量、双向变量的分类,逐个对变量的名称、类型(整型/单精度浮点数/双精度浮点数等)、维数、每一维需要读进/写出从核的大小、每一维的起始下标等进行明确,必要时可明确每一维的范围,制导文件中部分变量及数组见表1。
表1 制导文件中部分变量及数组示例
(3)增加条件编译语句(如计时统计和结果比较等)和调用master.c中函数的语句。
(4)部分优化方法的选项设置。
该工具的后端实现主要采用Rust语言作为开发语言,以Handlebars作为语义模板库,前端展示界面的开发采用Java语言。之所以选择Rust语言,是因为Rust是非GC的内存安全的语言,不像Java需要运行时有GC线程来清理内存,也不像C类语言需要手动申请、释放堆内存。它有一套自己的语法,按照这套语法编写自己的代码,它在编译时会自动地在需要的地方添加申请、释放内存的指令[12]。而内存安全的机制可以放心编写业务代码,不用担心空指、野指、堆内存不释放等问题,以提高程序的稳定性和安全性。
整个转换工具的输入是Fortran/C源程序和对应的制导文件。然后,对源码进行词法分析并用抽象语法树AST(abstract syntax tree)表示。下一步是根据转换后的抽象语法树生成目标语句。最后,将生成的目标语句转换为Athread源程序。
我们根据转换工具功能实现的流程逻辑,将项目划分为核心数据转换模块、模板模块、展示模块,这3个模块依次完成。
(1)核心数据转换模块
利用抽象语法树(AST)规则通过解析、转换、生成3个步骤[13]将Fortran/C文件和制导文件转换为内置数据模型。该工具在实现上将解析、转换和生成进行业务切割,其中,解析业务以数据序列化的方式实现,转换业务则是根据转换库预留接口实现,达到业务上的解耦,提高该工具的可扩展性。
1)解析,将原始语句解析为抽象语法树。
使用AST对原始语句解析时,需要先进行词法分析。词法分析会根据既定的语法单元表,将原始语句分割成一维数组语法单元列表(token表)。词法分析时会将连续的空格当做分割符,自动切分语法单元。获得token表之后,再使用语法分析,将一维无结构的token表转化为树形结构。在语法分析时,也会验证语法的正确性。
2)转换,操作抽象语法树节点完成转换。
AST转换没有固定的标准,本项目存在两种情况:对匹配节点简单的替换和对匹配子树结构的调整或者替换。这个过程包含遍历和转换两步。抽象语法树可以使用一般树的遍历方法,如先序遍历和后序遍历。
3)生成,根据转换后的抽象语法树生成目标语句。
在生成的遍历过程中,需要对所有不同类型语法单元的节点的所有情况定义不同的处理逻辑,而不同类型语法节点下子树的遍历顺序也有可能会不同。所以,需要为所有情况进行枚举。AST遍历示例如图2所示。
图2 AST遍历示例
其中,statement_list语法单元用来表示子节点都是并列依次执行的语句。while子树中包含了while循环的条件判断和if的语句块,if语句块包含了if的条件判断和两个赋值语句。在编译器构造同样类型语法树时,一般会使用与具体语言无关的语法单元的命名,这样在后续的转换只是对节点采用不同的翻译模式而已,做到了和具体语言的解耦。
(2)模板模块
在此模块中自定义模板组件,将数据转换模块中的内置数据格式转换为C语言文本。表2中列举了部分主要的模板组件。
表2 主要模板组件
(3)展示模块
单机版采用exe可执行文件直接生成导出文件到响应目录。Web版采用Web页面展示转换结果。在使用转换工具之前先把相应的f90/c源文件和制导文件上传,之后选定源文件和制导文件分别放到源文件代码编辑区和制导文件代码编辑区,然后保存,选择相关的模板开始转换,最后可以把生成的所有文件的压缩包下载,也可以在线预览生成的文件内容。Web原型设计如图3所示。
图3 Web版前端界面原型框架
Web版本中的两个编辑区可以实现在线的实时修改,同时我们添加了对“do”、“for”、“if”、“else”等关键词的高亮显示,更方便使用人员操作。
词法语法分析器主要是用于将一个编程语言转换成另外一种编程语言,相当于一个简单编译器的实现。按照需求该编译器包含词法分析、语法转换以及简单的错误分析,分析流程如图4所示。
图4 分析流程
2.1.1 词法语法分析
(1)语句分析器
分析器采用流式解析的方法,将整个代码的字符串按行读取,分析器按照规则匹配token表,在备选的语法结构中排除,直到匹配到语句结束并且备选的语法结构中只剩下一个备选。使用这种流式的语法解析方法可以减少扫描过多而带来的内存占用和时间问题。针对于一个语句多行编写的情况,会将语句之前的解析内容缓存,等到完整的语句解析完成之后再写入到数据结构中。按照制导文件和f90文件的结构可分为输入变量、输出变量、输入输出变量等,具体匹配流程如图5所示。
图5 语义匹配有限状态自动机
图5中虚线代表推测的是哪种语义。该流程图中的任意语句必须完全匹配才能确定是否为该语义,如果匹配出来的不在推测的集合中,则会返回错误信息。
对于不同的语言,该工具在解析规则上有较小的差别,只需更改相应的解析规则和模板就可以很好实现语言上的扩展。以C程序中的for循环为例,当输入一串字符串为 “for(i=0;i<10;i++)” 时,它会逐字进行判断,匹配过程如图6所示。
图6 for循环解析过程
(2)源文件语句分析
以f90源文件为例,系统需要提交的f90源文件主要用于解析变量、计算语句源语句、子程序识别。f90变量的解析主要用于提供变量的初始值。
根据f90源文件的结构,可以定义4个解析位置,分别为:开始子程序、解析变量、核心计算语句、结束子程序,并且可以循环。在f90中,语句的结束标识符为换行符,如果多行为一个语句,则行结束后为“&”标识符。
解析位置间的切换规则为:
1)文件初始时为解析位置为None。
2)当解析位置为None或结束子程序时,遇到 “subroutine”关键字则解析位置切换为开始子程序。
3)开始子程序行结束后无 “&”字符,则解析位置切换为解析变量。
4)当解析位置为解析变量时,遇到“……”行,则切换为计算源语句。
5)当解析位置为计算源语句时,遇到“end subroutine”时,切换为结束子程序。
(3)计算语句分析
计算语句主要进行格式的判断、转换,不牵扯到计算相关的运算符优先级相关的运算,所以此次解析只按照顺序将解析到的相关的数据存入到属性结构中即可。模板渲染时按照属性结构从上到下、从左到右的挨个转换渲染出来即可。
本文根据语句的特点,可以将语句分为循环语句、条件语句和普通语句,这3个语句分别都有自己重要的组成部分。在这3种语句中,其中循环语句和条件语句是树形结构,含有子集;普通语句为链表结构。目前采用对关键字的解析,通过解析规则对语句类型进行匹配。语法解析时按照 “do”、“if”、“else”、“end do”、“endif”等关键字判断语法类型,将不在关键字内但是符合变量命名规则的按照普通语句规则进行匹配并存入链表中;读取到“do”、“if”、“else”关键字时,后续的语句存入到相关结构体的children节点上,直到“end”结束,后续的解析存入到next节点上。
2.1.2 语义转换
语义转换部分采用模板渲染的方式。通过对比多个模板语言后采用handlebars作为该工具的模板语言,该模板语言具有高可扩展性以及具有简单的条件判断和循环判断能力,非常适合该工具的语义转换部分。
针对于handlebars开发多个定制化组件的过程中遇到的数据共享问题,我们采用写入、读取的方式实现数组间的数据共享,并且在文件的写入、读取时采用异步的方式进行,防止IO期间对于CPU占用导致的性能问题,提高程序性能。基本实现为每次语句转换生成的文件写入到的目录都是随机生成的,并且不会重复,所以我们在这个写入的文件夹中创建一个名为.cache 的隐藏文件中。当一个组件生成其它组件需要的数据时,则将该部分数据进行序列化并写入到.cache文件中;当其它组件需要读取时直接从.cache文件中读取数据进行反序列化操作即可。
2.1.3 错误分析
转换工具目前可以进行简单的语法错误分析,利用有限状态自动机来进行语法的错误分析,通过前面的输入会判定后面会有的可能性,如果待验证的语句都不属于任意可能性则会被判定为错误语法。
在按照神威系列超级计算机系统所提供的Athread库格式[1]编写典型的模板程序后,该工具将进行识别后形成包含变量的基准模板文件。
在这个过程中,我们首先进行AST解析,采用流式的思想将程序中的变量、语句相关的内容转换为内置的数据结构。然后我们对每个维度类型的神威众核代码的特性进行归纳总结,将程序中的整个代码看作一个字符串,每个字符串分为固定字符串和可替换字符串。固定字符包括数据类型和常用的函数名等,而可替换字符包括变量名、数组名和循环判断等。其中固定的字符串直接显示在模板上,可替换的字符串根据原数据类型或者变量使用循环组件、判断组件、变量组件等组件进行替换显示最终生成的基准模板文件,如图7(a)所示。
图7 从核模板代码(a)与生成代码(b)对比
模板代码根据需要生成的可替换数据,在对应位置以识别组件方式来进行代码的替换。具体体现在该工具通过对上述源文件和制导文件的词法语法解析,把解析数据转换成内部对象,通过把对象序列化成json串[14],交给handlebars渲染库,由handlebars统一调度。以从核代码slave.c为例,在生成slave.c文件的时候根据slave.c模板的组件调用相应的handlebars库,读取数据进行替换,转换效果如图7所示。
该转换工具目前实现了Fortran语言和C语言的二维模板、三维模板以及混合维度数组模板的转换。单机版采用exe可执行文件直接生成导出文件到相应目录。Web版采用Web页面进行操作和展示转换结果,转换效果如图8所示。
图8 Web界面二维模板转换效果
用户通过输入账号密码登录该平台,图8显示该平台的Web页面包含左侧一栏的文件管理区域,用户可以创建目录和子目录,将待转换的源文件和制导文件上传到用户创建的子目录中;中间一栏为代码文件编辑区,用户将子目录中的源文件添加到上侧的源文件代码编辑区,制导文件添加到下侧的制导文件文件编辑区,用户可以在这两个区域进行修改,修改完毕之后可以点击保存按钮保存到后台服务器的数据库中;右侧一栏为转换区,用户可以通过选择对应的数组维度信息对文件编辑区显示的源代码和制导文件进行相应的转换,转换成功后会出现如图8所示的一些Athread代码相关的源代码,并可以通过点击“下载压缩包”下载到本地。
该工具有很好的便利性,即通过上传源文件和编写好的制导文件到web界面即可进行代码转换,自动生成新的主程序、头文件、master.c和slave.c程序,用户不需要考虑神威环境下从核分配和LDM空间地址偏移等问题。
为了保证转换后的众核代码的准确性提供了部分检查功能,包括代码一致性检查、结果正确性检查。代码一致性检查指修改或合并之后的主核代码和从核代码内部及相互之间的一致性,包括下标、大小、数值等。目前通过主程序的结果正确性函数来验证转换后结果。
本文基于上述几个模板,将海洋数值模式中advu和advt的二维矩阵计算核心段由转换工具生成Athread格式的源代码及包含正确性检验的主程序在神威太湖之光平台上提交运行。运行结果部分效果如图9所示。
图9 二维模板正确性检验部分效果
图9显示的第一列数字表示原串行程序的执行结果和利用工具转换生成的众核程序执行结果的差值,实验结果显示,两个二维矩阵运算利用转换工具得到的众核代码在从核加速后的计算结果均与主程序串行执行的计算结果相同,验证了本文转换工具的正确性。
实验验证平台选择神威太湖之光超级计算机,操作系统为RaiseOS 2.0.5,其众核处理器的主核和从核工作频率均为1.5 GHz,访存带宽为136.51 GB/s[15]。
实验采用的二维代码为海洋数值模式中advu和advt的典型计算核心段。其中,二维数组规模为最小设为32×32,最大设为512×512,对应的数据量大小由64 kB增加到16 MB。
由于本文设计的转换工具的目的是解决前述提到的编写Athread代码所存在的问题,以达到或超过OpenACC*的性能。因此,本节比较了使用转换工具的Athread版本和使用OpenACC*人工编写获得的代码之间的性能比较,性能比较如图10和图11所示。
图10 两种编程模型的加速效果比较(单个核心段)
图11 两种编程模型的加速效果比较 (两个核心段)
根据图10和图11所示结果,自动生成的Athread代码比OpenACC*代码有更好的加速效果。虽然在2D模板中对一个核心段进行加速时,两种编程方法的加速性能差异不是很明显,这主要是由于核心段计算量小;但是如图11所示,当对2个核心段进行加速时,两种编程方法的加速比具有明显不同,这是由于OpenACC*对于多个核心段的并行粒度不高,只能分别进行加速;而转换工具采用一次spawn加载方式,并行粒度增大,从而产生差别明显的加速效果。通过上述实验可以验证这个转换工具是具有应用价值的。
自动转换的主从核代码与人工编写的代码相比具有很多优势,转换工具可以有效避免变量名称和大小写的低级书写错误;避免了结构体传参时变量顺序的错误;模板是自动计算地址偏移大小,可以有效防止人工编写代码计算从核地址偏移量时所发生的错误;此外,该工具还可直接调用已有的从核间通讯接口函数,方便用户使用,提高了代码的重用性。总体来说,该转换工具与人工编写相比,可以准确地生成Athread代码,提高开发人员的工作效率。
本文在国产神威系列超级计算机系统的架构下,泛化提出基于主程序调用master程序再由master程序调用slave程序的三层模板程序架构,集成了常用的众核优化方法,采用Rust语言进行词法和语法分析,设计开发了一个图形化使用界面的Athread代码自动转换工具。与现有的转换工具相比,该工具实现了在异构众核处理器上不同维度下Fortran/C程序核心段代码的基于Athread方式的自动众核代码转换。当前该工具采用基于制导文件的方法,下一步将采用成熟的编译器前端分析结果自动生成。同时,将继续集成一些新的优化方法、添加性能分析工具和支持一些新的语法格式。