掌桥专利:专业的专利平台
掌桥专利
首页

技术领域

本发明属于编译优化领域,其涉及一种编译自动向量化方法,具体涉及一种基于重排指令融合的编译自动向量化方法。

背景技术

CPU设计技术经历了长足地发展,单纯以提高主频的方式提升CPU性能的模式已经变得困难,利用并行性成为了提升CPU性能的重要途径。现代CPU能够通过特殊设计的硬件模块利用多种形式的并行性,如流水线与SIMD(Single Instruction Multiple Data,单指令流多数据流)扩展部件。例如,1996年,Intel率先在处理器中集成SIMD扩展部件,此后一直都在改进SIMD扩展部件,包括向量寄存器长度和指令集等,先后出现了MMX(Multi MediaeXtension,多媒体扩展指令集),SSE(StreamingSIMD Extensions,单指令多数据流扩展指令),AVX(Advanced Vector Extensions,高级矢量扩展指令集)和IMCI(Initial Many-Core Instructions,初始众核指令集)。

目前,CPU利用的并行性包括任务级并行(Task Level Parallelism),指令级并行(Instruction Level Parallelism)和数据级并行(Data Level Parallelism)。

目前,多核CPU往往通过增加多组独立的计算核心的方式提升CPU整体性能,增强了系统的任务级并行。但是一味地增加CPU核心数的方式并不可取,各个核心间数据通信的成本会随着核心数量的增加急剧增加。

指令级并行指的是单个核心内部所执行的指令流中相邻指令间存在的并行性,衡量CPU指令级并行性的性能指标是每周期执行指令数(Instructions Per Cycle,IPC)。现代CPU发展出了流水线技术,将指令的执行过程分为若干阶段,在同一时刻允许不同指令处在不同的执行阶段。

数据级并行是另一种存在于单个核心内的并行性。假定程序中存在大量同类型的运算,在不影响程序语义的前提下可以交换这些运算指令的顺序。如果能够将相同类型的运算指令集中一处,同时执行这些指令,程序的性能将得到提升。因为数据级并行性与CPU处理数据的规模相关,衡量CPU处理数据级并行的能力的性能指标是吞吐量(Throughput),即单位时间内CPU处理的数据的规模。对于计算密集型程序,程序的性能瓶颈一般受到CPU吞吐量的限制,数据级并行是提升这类程序性能的重要手段。

向量化计算是一种特殊的并行计算的方式,相比于一般程序在同一时间只执行一个操作的方式,它可以在同一时间执行多次操作,通常是对不同的数据执行同样的一个或一批指令,或者说把指令应用于一个数组/向量。

超字并行(Superword Level Parallelism,简写SLP)是数据级并行中一个应用较广的方法。超字并行向量化方法是在基本块内寻找指令并行机会,其过程是寻找基本块内相邻的内存访问,将其配对并打成向量化的包,再根据定义使用(Define-Use,DU)链和使用定义(Use-Define,UD)链将包扩展,然后按照向量化的要求组合同构操作,生成向量化代码。在具体实现时,将循环按照一定的迭代次数展开,寻找并将地址连续的数据打包作为向量来处理,再将程序中的同构语句用向量指令代替。还可将不同循环的语句合并到同一个基本块内,这样即可实现迭代间的语句并行,又可实现迭代内的指令并行,因此也就可实现迭代间和迭代内的数据SIMD向量化。

然而,现有的超字并行向量化方法具有高度的局部性,发掘并行性的能力受限。具体来说,现有技术构造向量数据流的过程十分简单,确定种子指令组后,仅简单地在UD链(use-def chain,使用-定义链)上延伸,不同种子指令组的处理完全独立,因而现有的超字并行向量化方法不具备发现不同种子指令组间并行性的能力。

此外,现有的超字并行向量化方法一般只处理具有相同向量化因子的指令组间关系,或者利用重排指令在向量化过程中不断地减小向量化因子,限制了向量化方法的应用范围。

发明内容

本发明的目的在于提供一种基于重排指令融合的编译自动向量化方法,以扩大向量化方法的应用范围。

为了实现上述目的,本发明提供一种基于重排指令融合的编译自动向量化方法,包括:

步骤S1:初始化一向量数据流,所述向量数据流用于记录指令组、指令组的索引和指令组间的依赖关系;

步骤S2:收集一个基本块中的Store指令,并按照访问的数据类型以及地址连续性来分组,最终形成若干个Store指令组,每一个Store指令组的所有Store指令访问一段连续地址空间且访问的数据宽度相同;以所有Store指令组作为种子指令组加入同一个向量数据流,同时,对每一个Store指令组赋予索引;

步骤S3:以步骤S2构造的带有种子指令组的向量数据流为起点,构造所有可行的向量数据流,并从中选择最优的向量数据流;

步骤S4:判断步骤S3得到的判断最优的向量数据流的代价是否大于0,如果大于0,则放弃向量化;否则,根据最优的向量数据流进行实质的程序的向量化变换。

优选地,所述步骤S2包括:

步骤S21:通过Store指令收集模块寻找基本块中的Store指令;

步骤S22:检查这些Store指令各自访问的地址之间的依赖关系,将访问的地址之间没有依赖关系的多个Store指令作为能够并行执行的Store指令来分为一组,得到多组经过初次分组的Store指令;对于上述的每一组经过初次分组的Store指令,根据Store指令访问的地址的连续性进行进一步的排序分组,得到多个Store指令组;

步骤S23:以所有的Store指令组均作为种子指令组加入同一个向量数据流,同时,对于每一个Store指令组,按照访问的地址从小到大排列的方式赋予索引,以使得其中的所有Store指令各自具有不同的索引值。

优选地,在所述步骤S22中,进行进一步的排序分组,包括:将同一组经过初次分组的Store指令中访问一段连续地址空间且访问的数据宽度相同的多个Store指令作为一个Store指令组,将其中访问的数据宽度不同或者访问的地址空间不连续的多个Store指令拆分为不同的Store指令组。

优选地,在所述步骤S3中,进行向量数据流的构造,包括:

步骤S31:将所有种子指令组加入向量数据流后得到的向量数据流作为初始的向量数据流,将初始的向量数据流作为本级递归的向量数据流并将当前的本级递归作为第0级递归;

步骤S32:将本级递归的最优返回向量数据流和本级递归的候选指令组集合均设置为无效值;

步骤S33:根据本级递归的向量数据流构造本级递归的候选指令组的集合;

步骤S34:判断本级递归的候选指令组的集合是否为空;若本级递归的候选指令组的集合判断为空,以本级递归的向量数据流作为最下级递归时的本级递归的最优返回向量数据流,随后执行步骤S36;否则,备份本级递归的向量数据流、候选指令组的集合和最优返回向量数据流;随后,执行所述步骤S35;

步骤S35:从本级递归的候选指令组的集合中选取一尚未被取过的候选指令组,将该候选指令组加入本级递归的向量数据流,以使得本级递归的向量数据流的递归等级加一;随后转入步骤S32;

步骤S36:从备份还原出本级递归的向量数据流所在的上一级递归的所对应的向量数据流、候选指令组的集合和最优返回向量数据流,并将本级递归的最优返回向量数据流递归作为返回值返回给其上一级递归;

步骤S37:将所述步骤S36中的上一级递归作为新的本级递归,计算步骤S36的返回值的索引和代价,将其与本级递归的最优返回向量数据流的代价相比较,根据比较结果更新本级递归的最优返回向量数据流;

步骤S38:确定本级递归的向量数据流是否其所有候选指令组的选取分支均计算完毕,若尚未计算完毕,则回到所述步骤S35;否则,回到步骤S36以进一步返回至上一级递归,直到不存在上一级递归,输出此时的本级递归的最优返回向量数据流及其代价作为最优的向量数据流及其代价。

优选地,在所述步骤S37中,本级递归的向量数据流的索引和代价是通过增量计算向量数据流的索引和代价的增量结果来计算的;

且如果本级递归的最优返回向量数据流为无效值或者本级递归的最优返回向量数据流的代价高于返回值的代价,则将本级递归的最优返回向量数据流更新为返回值;否则,本级递归的最优返回向量数据流保持不变。

优选地,在所述步骤S37中,先计算索引的增量结果,再计算代价的增量结果。

优选地,所述增量计算是指计算步骤S35所选取的候选指令组的索引和代价,并将其与本级递归的向量数据流所在的选取分支尚未计算完毕的那一级递归的向量数据流之后所有后续加入的候选指令组和所有重排指令的索引和代价相加,作为本级递归的向量数据流的索引和代价的增量结果。

优选地,步骤S35所选取的候选指令组的索引的计算步骤如下:

步骤S371:将加入的候选指令组作为当前指令组,确定当前指令组是否是Load指令组,如果是,则对其赋予索引;

步骤S372:确定当前指令组与本级递归的向量数据流的其他指令组之间的依赖关系,以确定当前指令组所依赖的各个指令组;如果当前指令组所依赖的某个指令组的大小与当前指令组的大小不符,则在这二者之间插入一条重排指令;如果当前指令组与存在依赖关系的某一指令组大小相同,且相关的指令组已经设置了索引,则将当前指令组的索引以相关的指令组的索引为基础来导出;

步骤S373:判断此时当前指令组是否设置索引,如果此时当前指令组没有设置索引,则给当前指令组赋予任意索引。

优选地,所述候选指令组是指与给定的不完整的向量数据流中的指令组有可解析的依赖关系的指令组;候选指令组的集合则是指所有满足候选指令组的条件的指令组的集合;

所述的可解析的依赖关系指:两指令组如果各自替换为一条SIMD指令,则两SIMD指令间可以直接建立或者通过插入一条重排指令来保持程序原始语义的依赖关系。

优选地,在所述步骤S33中,根据本级递归的向量数据流构造本级递归的候选指令组的集合,包括:

步骤S331:初始化得到一个指令组的集合,作为组合空间;

步骤S332:对于本级递归的向量数据流中Load指令组除外的每一子指令组和所述子指令组中所有指令的每一个参数位置,取单个参数位置上的所有参数的所对应的所有指令形成一个初始指令组;

步骤S333:判断初始指令组中的指令是否均具有相同的类型;如果初始指令组中的指令均具有相同的类型,则将所述初始指令组直接加入组合空间;如果初始指令组中的指令不都具有相同的运算类型,则检查初始指令组中的指令是否能够分为两个拆分指令组,且每个拆分指令组具有相同的运算类型,如果是则将这两个拆分指令组分别加入组合空间;

步骤S334:在组合空间中,将具有相同运算类型且具有相同大小的指令组间两两配对,合并成同一个指令组;

步骤S335:将组合空间中的每一指令组分别进行Load指令判断,并根据Load指令判断结果来将其加入候选指令组的集合或者丢弃;

其中,进行Load指令判断包括:判断指令组是否是Load指令组以及Load指令是否访问相同大小的、连续的地址空间;如果指令组的Load指令判断结果是:指令组是Load指令组,且Load指令不访问相同大小的、连续的地址空间,则指令组丢弃,不加入候选指令组集合;否则,将指令组加入候选指令组的集合。

本发明的基于重排指令融合的编译自动向量化方法将基本块内所有具有向量化条件的种子指令组纳入同一个向量数据流构造过程,使得生成的候选指令组可以是具有并行性的指令间任意组合,可以一次向量化一个基本块并且可以发现不同种子指令组间的并性行,为程序生成具有更高向量化因子的程序,进一步提升程序的吞吐量。

本发明的基于重排指令融合的编译自动向量化方法能够利用重排指令整合基本块内的不同表达式提升向量化因子,能够充分地利用大容量的SIMD寄存器,提升向量化的程序的吞吐量。本发明相将重排指令作为一种桥接具有不同向量化因子的指令组的手段,极大地拓宽了向量化程序的形式。而现有方法一般只处理具有相同向量化因子的指令组间关系,或者利用重排指令在向量化过程中不断地减小向量化因子,本发明更加充分地利用了重排指令,不仅支持动态地减小向量化因子,也支持动态地扩张向量化因子,因而能够更加灵活地适应复杂的程序间依赖结构,扩大向量化方法的应用范围。

附图说明

图1为本发明的一种基于重排指令融合的编译自动向量化方法的流程图;

图2为本发明的所述的向量数据流间依赖关系的描述示意图。

具体实施方式

下面结合附图,给出本发明的一个实施例,对本发明的方案做进一步详细的解释和说明。

如图1所示,本发明的基于重排指令融合的编译自动向量化方法主要基于以下原理:首先对于一个基本块,构造所有存在向量化潜力的向量数据流,并根据代价函数评估每一个向量数据流的代价,从中选出具有最低代价的向量数据流。如果代价最低的向量数据流的代价为负,即向量化的程序相对原始程序有性能优势,则参照向量数据流将基本块中预备被向量化的指令组替换为SIMD指令,并插入必要的重排指令(即Shuffle指令)。

如图1所示为一种基于重排指令融合的编译自动向量化方法,其步骤为:

步骤S1:初始化一向量数据流,所述向量数据流用于记录之后的向量化过程中确定的指令组、指令组的索引和指令组间的依赖关系;向量数据流是向量化过程维护的核心数据结构,指令组和指令组间的依赖关系逻辑上视为一个有向无环图,其中指令组是点,依赖关系是边;而指令组的索引是对每个指令组附加的额外信息(对指令组中每一指令,映射了一个数字)。

其中,指令组的索引是人为定义的。指令组间的依赖关系,具体是不同指令组中的指令间的依赖关系,如果一对有依赖关系的指令分属两个指令组,则这两个指令组有依赖关系,依赖关系的方向和这两个指令间的依赖关系相同。

步骤S2:进行Store指令收集:收集一个基本块中的Store指令,并按照访问的数据类型以及地址连续性来分组,最终形成若干个Store指令组,每一个Store指令组的所有Store指令访问一段连续地址空间且访问的数据宽度相同;以所有Store指令组作为种子指令组加入同一个向量数据流,同时,对每一个Store指令组赋予索引;

其中,Store指令为存储指令,Load指令为加载指令。

所述步骤S2包括:

步骤S21:通过Store指令收集模块寻找基本块中的Store指令;

这些Store指令是用于在随后的步骤中建立种子指令组。

步骤S22:检查这些Store指令各自访问的地址之间的依赖关系(即,检查Store指令中是否存在访问同一地址的情况,这是一种隐式的依赖关系,访问同一地址的多个store指令间不具有并行性),将访问的地址之间没有依赖关系的多个Store指令作为能够并行执行的Store指令来分为一组,得到多组经过初次分组的Store指令;对于上述的每一组经过初次分组的Store指令,根据Store指令访问的地址的连续性进行进一步的排序分组,得到多个Store指令组。

其中,进行进一步的排序分组,包括:将同一组经过初次分组的Store指令中访问一段连续地址空间且访问的数据宽度相同的多个Store指令作为一个Store指令组,将其中访问的数据宽度不同或者访问的地址空间不连续的多个Store指令拆分为不同的Store指令组。如果是同一组经过初次分组的Store指令中存在多个Store指令访问的数据宽度不同,或者访问的地址空间不连续,则在进行进一步的排序分组时,应该分为两个Store指令组。这里的进一步的排序分组,是由于数据类型不同或者访问的地址不连续的Store指令组都无法替换为SIMD指令。

由此,因为地址是连续的,Store指令组中的Store指令所生成的SIMD指令可以一次写入所有数据,所以要求被写入的数据按照地址从小到大排列,可以视为Store指令是并行发生的,load指令同理。

由此,最终得到多个Store指令组,其中每一个Store指令组中的所有Store指令都是可并行执行的,并且同一个Store指令组中的各个Store指令按访问的地址的连续性自小到大排序。

步骤S23:以所有的Store指令组均作为种子指令组加入同一个向量数据流,同时,对于每一个Store指令组,按照访问的地址从小到大排列的方式赋予索引,以使得其中的所有Store指令各自具有不同的索引值。受限于SIMD硬件特性,规定Store指令组都具有强制的索引(对于连续地址上的n个Store指令,由地址从小到大排列,分别赋予0到n-1)。

其中,索引是指令组内指令的排布关系,是对指令组内每一指令赋予的唯一编号,类似于指令组是向量,指令组中的指令是分量,索引就是分量的位置。由此,得到的向量数据流中记录了每一个种子指令组的索引,但索引的指定时间可能晚于Store指令组加入向量数据流的时间。

由此,可以一次处理基本块内所有的种子指令组,不同于现有方法一次处理一个种子,本发明的基于重排指令融合的编译自动向量化方法为指令组间的任意组合、最大化并行性提供了前提。

步骤S3:进行向量数据流构造:以步骤S2构造的带有种子指令组的向量数据流为起点,构造所有可行的向量数据流,并从中选择最优(即代价最小)的向量数据流;

其中,每一个向量数据流均代表了一个向量化方案,向量数据流记录了向量化方案中的指令组,指令组的索引,以及指令组间的依赖关系。一个基本块的向量化方案可能不止一个,对应的,向量数据流也具有多样性,为了得到最优的向量化方案,需要构造所有可行的向量数据流,来描述出所有可行的向量化方案。由此,不同于现有的贪心方法,在需要决定如何从多种构造方式中选择时,基于局部的、启发式的方法选择一种构造方式,并放弃对其它构造方式的探索,不利于寻找最优解,本发明的步骤S3构造向量数据流的过程构造了所有可能的向量数据流,并且从中选择了最优的向量数据流(代价模型意义下的),可以确保寻找到的是最优解。

在所述步骤S3中,按照向量数据流的构造方法,来构造所有可行的向量数据流,并从中选择最优的向量数据流。其中,向量数据流的构造方法是指给定一个基本块,递归枚举所有可能的向量数据流,并从中选择最优的向量数据流的过程。因此,在所述步骤S3中,构造所有可行的向量数据流,并从中选择最优的向量数据流,包括以下步骤:

步骤S31:将所有种子指令组加入向量数据流后得到的向量数据流作为初始的向量数据流(此时的向量数据流是不完整的向量数据流),将初始的向量数据流作为本级递归的向量数据流并将当前的本级递归作为第0级递归;

其中,作为本级递归的向量数据流的初始的向量数据流,由于其在递归开始之前已经在该步骤S31中构造完成,因此作为第0级递归的向量数据流。任意一级递归的向量数据流在调用下一级递归之前,都是不完整的,由下一级递归继续扩张,而下一级递归返回时,返回的都是完整的、可计算索引和代价的向量数据流。

步骤S32:将本级递归的最优返回向量数据流和本级递归的候选指令组集合均设置为无效值;由此,实现了本级递归的最优返回向量数据流以及本级递归的候选指令组集合的初始化。

其中,对于各个不同的递归等级来说,本级递归的最优返回向量数据流用于储存单个向量数据流的所有信息,包括所有的指令组和指令组间依赖关系,但是其索引在逐级向上返回的过程中,本级递归的最优返回向量数据流的索引才增量地计算。

步骤S33:根据本级递归的向量数据流构造本级递归的候选指令组的集合;

所述步骤S33中的根据本级递归的向量数据流构造本级递归的候选指令组的集合将在下文详述。

步骤S34:判断本级递归的候选指令组的集合是否为空;根据判断结果,若本级递归的候选指令组的集合判断为空,说明完成了一个完整的向量数据流的构造,以本级递归的向量数据流作为最下级递归时的本级递归的最优返回向量数据流,随后执行步骤S36(即跳过所述步骤S35);否则,说明集合不为空,备份本级递归的向量数据流、本级递归的候选指令组的集合和本级递归的最优返回向量数据流;随后,执行所述步骤S35;

由此,得到的本级递归的最优返回向量数据流的本级递归的向量数据流且返回的本级递归的向量数据流为一个完整的向量数据流。由于此时是递归级数最低的节点(即递归树叶节点)的数据,其是本级递归的唯一一个向量数据流,因此其也必定是递归级数为最低一级时的本级递归的最优返回向量数据流。

需要说明的是,最下级递归,意味着递归等级的数值最大;而第0级递归,则是最上级的递归。

步骤S35:从本级递归的候选指令组的集合中选取一尚未被取过的候选指令组(任意顺序),将该候选指令组加入本级递归的向量数据流,以使得本级递归的向量数据流的递归等级加一;随后转入步骤S32;

其中,备份的向量数据流是整个向量数据流,即,即其中的指令组,指令组间依赖关系,以及部分指令组的索引(此时只有Store指令组具有索引)。

尚未被取过的候选指令组,指的是对于本级递归的向量数据流,之前从未进行过取该候选指令组加入本级递归的向量数据流的操作。

由此,步骤S34在集合不为空时保存本级递归的向量数据流的递归上下文。其中,递归上下文包括:1)本级递归的向量数据流;2)候选指令组集合,每一级递归的候选指令组集合都是根据下一级递归开始时的当前向量数据流计算的,相互之间无关,在递归加深和返回时要保存和恢复;3)本级递归的最优返回向量数据流,类似2)候选指令组集合,对应于不同的递归等级的本级递归的最优返回向量数据流的需要加以区分。

步骤S36:以本级递归的最优返回向量数据流作为返回值返回给其上一级递归;也就是说,从备份还原出本级递归的向量数据流所在的上一级递归所对应的向量数据流(上一级递归的向量数据流是非完整的向量数据流)、候选指令组的集合和最优返回向量数据流,并将本级递归的最优返回向量数据流递归作为返回值返回给其上一级递归;由此,实现了递归返回。

步骤S37:将所述步骤S36中的上一级递归作为新的本级递归(也就是说,上一级递归所对应的向量数据流、候选指令组的集合和最优返回向量数据流,就是新的本级递归的向量数据流、候选指令组的集合和最优返回向量数据流),计算步骤S36的返回值(此时返回值是新的本级递归的向量数据流的“下一级递归最优返回向量数据流”)的索引和代价,将其与本级递归的最优返回向量数据流的代价相比较,根据比较结果更新本级递归的最优返回向量数据流;

其中,如果本级递归的最优返回向量数据流为无效值或者本级递归的最优返回向量数据流的代价高于返回值的代价,则将本级递归的最优返回向量数据流置为所述返回值,从而实现更新;否则,本级递归的最优返回向量数据流保持不变。

由此,可以以类似于淘汰赛的模式比较和选择最优返回向量数据流,即即每一级递归的向量数据流都要选出一个本级递归的最优返回向量数据流返回其上一级递归,然后上一级递归(一般地,具有多个候选指令组,每一个候选指令组导致了一次下一级递归,即导致下一级递归返回一个“下一级递归的最优返回向量数据流”)从所有的其下一级递归的最优返回向量数据流中选出一个最优的,参与更上一级递归的比较。

需要说明的是,本级递归的当前向量数据流(通常不完整)和本级递归的最优返回向量数据流(是完整的)是两个不同的对象。在本级递归的向量数据流中,向上一级递归的向量数据流返回的返回值(即本级递归的最优返回向量数据流)与上一级递归自身的向量数据流应当视为不同的对象,相互不覆盖。本级递归的向量数据流通常为不完整的向量数据流(例如初始的向量数据流为第0级递归的向量数据流);而本级递归的最优返回向量数据流用于通过不断的更新来存储以本级递归的向量数据流为头部,所形成的所有完整的向量数据流中最优(即,代价最小)的一个。

步骤S38:确定本级递归的向量数据流是否其所有候选指令组的选取分支均计算完毕,若尚未计算完毕,则回到所述步骤S35,以进一步计算其余的选取分支;否则,说明其所有候选指令组的选取分支计算完毕,回到步骤S36以进一步返回至上一级递归,直到第0级递归的所有候选指令组的选取分支也计算完毕,不存在上一级递归,此时流程结束,计算本级递归的向量数据流中的种子指令组的索引和代价来计算本级递归的最优返回向量数据流,并输出此时的本级递归的最优返回向量数据流及其代价作为最优的向量数据流及其代价。

在所述步骤S37中,本级递归的向量数据流的索引和代价是通过增量计算向量数据流的索引和代价的增量结果(除了步骤S38流程结束之外的情况)来计算的。

在所述步骤S37中,涉及到索引和代价的计算。索引和代价的计算方式如下:

索引的计算和代价的计算存在先后关系,因为不同的索引方案会引入不同的重排指令(即Shuffle指令),导致不同的代价,故而需要首先计算索引的增量结果,再计算代价的增量结果。

其中,代价模型是一种对代码执行时间的静态评估方法,其赋予每个指令一个代价值;向量数据流中的一个指令组可以看作原始程序中的一组指令进行标量和向量化后替代这一组指令的一个SIMD指令,这一指令组的相对代价就是后者的代价减去前者的代价(即替代一指令组的一个SIMD指令的代价减去该指令组的每个指令的代价);一个向量数据流的代价就是该向量数据流的所有指令组的相对代价和所有重排指令的代价的加和。如果总代价小于0,则根据代价模型这一向量数据流所代表的向量化方案有收益。

如果存在多种不同的向量数据流,则分别计算各种向量数据流所各自涉及的指令的代价总和,并以这样的代价综合比较不同版本程序(即不同的向量数据流)的性能优劣。

逻辑上,前述的向量数据流的构造方法构成了向量数据流的解空间,将递归看成树,每一次递归都对应于一颗子树,显然,初始递归对应于整棵树。每一次递归的若干次子递归(即下一级递归)都是对应的子树的子树;同时,每一次递归对应于一个备份的向量数据流,这里将该备份的向量数据流对应于这一次递归所对应的子树的根节点;每个子树包括递归等级依次增加的一个根节点、至少一个中间节点和至少一个叶节点,此时,子树中的每一个中间节点,对应于一个非完整的向量数据流,每一个叶节点对应于一个完整的向量数据流。特别地,每一个节点对应的向量数据流是其子孙节点对应向量数据流的一部分;反过来,每一节点所对应的向量数据流也是由其祖先节点的向量数据流扩张得到的;指定一个节点,其不同子节点对应的向量数据流的相同部分恰恰是这一节点对应的向量数据流。

出于比较代价的目的,只需要计算两向量数据流差异部分的索引和代价,避免计算相同部分的索引和代价,可以避免重复计算。(递归向上逐级返回时,每一级都在所有兄弟节点对应的向量数据流中选择最优,非最优的向量数据流自此被抛弃)。

故而,如上文所述,在所述步骤S37中,本级递归的向量数据流的索引和代价是通过在整个递归返回过程后(即步骤S38返回至步骤S35后)增量计算来得到的。增量计算是指计算在步骤S35所选取的候选指令组的索引和代价,并将其与选取候选指令组之前的那一次步骤S38回到步骤S35时的本级递归的向量数据流(即本级递归的向量数据流所在的选取分支尚未计算完毕的那一级递归的向量数据流)之后所有后续加入的候选指令组和所有重排指令的索引和代价相加,作为本级递归的向量数据流的索引和代价的增量结果。由此,可以获得递归返回到选取分支尚未计算完毕的向量数据流,其所有后续加入的候选指令组的索引和代价,这部分索引和代价构成了以上述的选取分支尚未计算完毕的向量数据流作为一部分的任意两个完整的向量数据流的差异部分的索引和代价,可以通过这一部分代价对这两个向量数据流进行比较。

索引的计算过程实质上是分析插入重排指令的过程。步骤S35所选取的候选指令组的索引的计算步骤如下:

步骤S371:将加入的候选指令组作为当前指令组,确定当前指令组是否是Load指令组,如果是,则对其赋予索引;

其中,如果当前指令组是Load指令组,则类似于Store指令组,根据地址连续性对当前指令组赋予索引;

根据候选指令组生成过程,向量数据流中的Load指令组必然是按地址连续的;其中,按地址连续的每一个指令组的索引是独立命名的,对于一个指令组,其中有n条标量指令,则索引的取值为0到n-1。

步骤S372:确定当前指令组与本级递归的向量数据流的其他指令组之间的依赖关系,以确定当前指令组所依赖的各个指令组;如果当前指令组所依赖的某个指令组的大小与当前指令组的大小不符,则在二者之间插入一条重排指令(即在向量数据流中记录,这一依赖关系对应于一条重排指令);如果当前指令组与存在依赖关系的某一指令组大小相同,且相关的指令组已经设置了索引,则将当前指令组的索引以相关的指令组的索引为基础来导出;

将当前指令组的索引以相关的指令组的索引为基础来导出,这是为了保证二个指令组在同一位置上的指令间具有依赖关系(SIMD硬件约束),此时其中一个指令组的索引可以决定另一个指令组的索引。

步骤S373:判断此时当前指令组是否设置索引,如果此时当前指令组没有设置索引(即上述的条件都不满足),则可以给当前指令组赋予任意索引。

由此,索引计算时确定了需要插入的重排指令,即与当前指令组相关的代价也可以计算了。

下面详述在所述步骤S33中,构造本级递归的候选指令组的集合的具体方式。

其中,候选指令组是指与给定的不完整的向量数据流中的指令组有可解析的依赖关系的指令组;候选指令组的集合则是指所有满足候选指令组的条件的指令组的集合。

所述的可解析的依赖关系指:两指令组如果各自替换为一条SIMD指令,则两SIMD指令间可以直接建立或者通过插入一条重排指令(即Shuffle指令)来保持程序原始语义的依赖关系。能否直接建立依赖取决于硬件支持,普遍要求两SIMD指令的向量化因子相同。插入重排指令可以调整依赖关系,以满足硬件约束,但是引入额外代价,因而需要限制插入重排指令的数量。不能满足上述条件,即依赖关系不可解析,无法将两指令组分别替换为SIMD指令且维持程序原始语义。(计算机程序观察两指令组的结构(向量化因子的大小)和指令组内标量的依赖关系来判断是否可解析,重排指令所支持的插入模式参见下图2。

由此,本发明通过应用重排指令,扩大了向量化的普适范围,使得本身不符合现有的SIMD指令间依赖关系规格(即两SIMD指令对应的两指令组大小必须相同)的指令组间依赖可以通过应用重排指令合规化;现有方法还没有将重排指令的运用推广成一种普适的方法,仅有一些匹配特定模式的应用。

不完整的向量数据流通过不断地选取候选指令组加入来扩张自身,直至向量数据流中无法选出候选指令组。候选指令组的集合的大小如果大于1(即候选指令组的集合中的候选指令组的数量大于1),则上述递归存在分支,向量数据流存在多样性。

在所述步骤S33中,根据本级递归的向量数据流构造本级递归的候选指令组的集合,包括以下步骤:

步骤S331:初始化得到一个指令组的集合,作为组合空间;

此时得到的组合空间为空集。

步骤S332:对于本级递归的向量数据流中每一子指令组(Load指令组除外)中所有指令的每一个参数位置(一元运算有一个参数位置,二元运算有两个参数位置),取单个参数位置上的所有参数的所对应的所有指令形成一个初始指令组;

需要说明的是,此处应当排除子指令组本身,只取其参数形成的指令组参与组合空间构造,从而避免子指令组重复加入初始指令组的问题。

步骤S333:判断初始指令组中的指令是否均具有相同的类型;如果初始指令组中的指令均具有相同的类型,则将所述初始指令组直接加入组合空间(其中,运算类型包括所有的二元运算指令类型、所有的类型转换指令类型,所有的一元运算指令类型、Load指令类型等等);如果初始指令组中的指令不都具有相同的运算类型,则检查初始指令组中的指令是否可以分为两个拆分指令组,每个拆分指令组具有相同的运算类型,如果是则将这两个拆分指令组分别加入组合空间;

需要说明的是,如果分为两个拆分指令组时仍然无法保证指令不都具有相同的运算类型,则说明此时这一指令组不具有继续向量化的条件,不再参与组合空间的构造。

步骤S334:在组合空间中,将具有相同运算类型且具有相同大小的指令组间两两配对,合并成同一个指令组;

由此,使得组合空间中的两个小的指令组合并为数量更少的大尺寸的指令组。并且,两个指令组而非多个指令组合并为一个指令组,避免了导致相应操作的执行代价过大或者组合空间的规模可能急剧增加的问题。

步骤S335:将组合空间中的每一指令组分别进行Load指令判断,并根据Load指令判断结果来将其加入候选指令组的集合或者丢弃;

其中,进行Load指令判断包括:判断指令组是否是Load指令组以及Load指令是否访问相同大小的、连续的地址空间。如果指令组的Load指令判断结果是:指令组是Load指令组,且Load指令不访问相同大小的、连续的地址空间,则指令组丢弃,不加入候选指令组集合;否则,将指令组加入候选指令组的集合。

上述枚举方式保证了任意的候选指令组和现有向量数据流中的指令组的依赖关系都是可解析的。

由此,本发明通过在步骤S31将所有种子指令组加入向量数据流,且在步骤S33构造本级递归的候选指令组的集合时,组合枚举了所有可以并行执行的相同运算的指令,发掘了不同指令组间存在的并行性,这是现有算法无法做到的,因为现有算法每次只处理一个种子指令组,无法发掘不同种子指令组的UD链上的指令组间的并行性。此外,本发明在步骤S33构造本级递归的候选指令组的集合时,候选指令组顾及了不同的指令组大小,配合递归构造向量数据流的过程,实现了一个向量数据流中指令组大小随程序结构动态调整,充分地探索了可能的向量数据流(即探索了不同的向量化方案);现有的方法往往固定特定的指令组大小,以简化设计。

步骤S4:判断步骤S3得到的判断最优的向量数据流的代价是否大于0,如果大于0,则放弃向量化;否则,认为向量化是有性能收益的,根据最优的向量数据流进行实质的程序的向量化变换。

其中,一个向量数据流的代价就是该向量数据流的所有指令组的相对代价和所有重排指令的代价的加和。每个指令组的相对代价就是替代一指令组的一个SIMD指令的代价减去该指令组的每个指令的代价。

进行实质的程序的向量化变换包括:以最优的向量数据流所对应的向量化方案来执行SIMD指令生成与替换的方法。

SIMD指令生成与替换的方法的具体步骤如下:

步骤S41:根据最优的向量数据流中指令组和指令组间的依赖关系对原始的基本块进行排序,以确定基本块中的指令在排序后的顺序;所述指令在排序后的顺序使向量化方案中的同一个指令组的各个指令在排序后的基本块中连续排布,且不同指令组中的指令间的顺序不违背两者所在的指令组间的依赖关系。

步骤S42:根据最优的向量数据流中记录的指令组和索引来为其中的每一指令组生成对应的SIMD指令,并根据最优的向量数据流中的记录在需要的SIMD指令间插入重排指令。

步骤S43:完成SIMD指令生成后,删除所有被SIMD指令替代的标量指令,向量化完成。

本发明未详细阐述部分属于本领域技术人员的公知技术。

以上所述,仅为本发明中的具体实施方式,但本发明的保护范围并不局限于此,任何熟悉该技术的人在本发明所揭露的技术范围内,可理解想到的变换或替换,都应涵盖在本发明的包含范围之内,因此,本发明的保护范围应该以权利要求书的保护范围为准。

相关技术
  • 一种基于重排指令融合的编译自动向量化方法
  • 一种编译器后端指令选择学习系统及具有该系统的编译器和指令选择方法
技术分类

06120113678794