首页> 中国专利> 用于对非叶代码进行基于编译器的矢量化的系统和方法

用于对非叶代码进行基于编译器的矢量化的系统和方法

摘要

本发明描述了用于软件应用的矢量化的系统和方法。在一些实施例中,源代码依赖关系可以按可以扩展编译器的能力以矢量化否则为标量的函数的方式来表达。例如,当编译被调用函数时,编译器可以识别被调用函数与除了传递至该被调用函数的参数以外的其它变量的依赖关系。该编译器可以记录这些依赖关系,例如,记录在依赖关系文件中。稍后,当编译调用该被调用函数的调用函数时,同一(或另一)编译器可以引用先前识别的依赖关系,并将它们用于确定是否和怎样矢量化调用函数。具体来说,这些技术可以辅助非叶循环的矢量化。因为非叶循环相对常见,所以在此描述的技术可以增加可以被应用至许多应用的矢量化的量。

著录项

  • 公开/公告号CN103119561A

    专利类型发明专利

  • 公开/公告日2013-05-22

    原文格式PDF

  • 申请/专利权人 苹果公司;

    申请/专利号CN201180045583.4

  • 发明设计人 J·E·戈尼诺;

    申请日2011-09-07

  • 分类号

  • 代理机构中国国际贸易促进委员会专利商标事务所;

  • 代理人曹瑾

  • 地址 美国加利福尼亚

  • 入库时间 2024-02-19 19:20:08

法律信息

  • 法律状态公告日

    法律状态信息

    法律状态

  • 2016-03-09

    授权

    授权

  • 2013-06-19

    实质审查的生效 IPC(主分类):G06F9/45 申请日:20110907

    实质审查的生效

  • 2013-05-22

    公开

    公开

说明书

技术领域

本公开涉及计算机系统,并且更具体地说,涉及用于使能软件应 用的通用矢量化的系统和方法。

背景技术

典型软件开发范例是公知的。计算机程序员采用高级编程语言 (例如,Basic、C++等)来编写源代码。在某一点处,程序员使用编 译器将源代码变换成目标代码。在变换成可执行代码之后(例如,在 链接或其它编译时间或运行时间处理之后),接着,所得目标代码可以 通过计算机或计算装置来执行。

计算机如今具有多个处理单元,并且能够并行地执行指令。利用 这种架构的优点,现代编译器可以尝试“并行化”或“矢量化”特定软件 功能,从而代替使单一处理单元顺序地一次执行一个指令,多个处理 单元可以同时执行多个指令。

在编译处理期间,编译器分析软件功能以确定是否存在针对矢量 化的任何障碍。一个这种障碍例如是存在真实数据依赖关系 (dependency)。这在当前指令引用通过执行在前指令所获取的数据时 发生。在该情况下,较后指令仅可以在较早指令之后执行,并由此两 个指令不能并行执行。另一潜在障碍是存在函数调用。例如,如果要 编译的函数对外部函数进行调用,则编译器不能够矢量化该调用函数。

发明内容

本公开提供了用于使能通用矢量化软件应用的系统和方法。为 此,在此公开的系统和方法提供对扩展编译器的能力以矢量化函数的 依赖关系和/或接口的表达。

在一非限制实施例中,编译器可以在其编译期间检查存储器和/ 或一函数(“被调用函数”)内的数据依赖关系,并且在依赖关系数据 库(举例来说,如依赖关系文件)中表达那些依赖关系。一旦编译, 被调用函数就可以例如变为库函数等。在稍后的时间点,可以创建另 一函数(“调用函数”),以使其针对被调用函数进行调用。在编译调用 函数期间,编译器可以访问与被调用函数相关联的依赖关系文件,并 且可以识别其依赖关系。基于被调用函数的依赖关系,编译器可以进 行有关是否矢量化调用函数的判定。

另外或另选的是,编译器可以判定仅矢量化调用函数的一部分。 与其它可能方式相比,通过使用依赖关系文件而提供的可见性可以允 许编译器矢量化更高百分比的函数。

例如,实现依赖关系文件允许矢量化包括非叶循环(non-leaf  loop)(即,对源代码不可见的外部函数进行调用的循环)的函数。因 为现今大多数软件函数包括一个或多个非叶循环,所以这些系统和方 法可以增加可以被应用至任何应用的矢量化的量。

在另一非限制实施例中,编译器可以根据单一源代码描述生成函 数的标量和矢量形式。函数的标量形式可以使用如由源代码最初指定 的标量接口。同时,函数的矢量形式可以实现针对该函数的、用于接 收矢量参数和生成矢量返回值的矢量接口。

例如,矢量接口可以在与函数相关联的依赖关系文件中暴露。这 种另选矢量接口的存在例如允许编译器从矢量化循环内进行矢量函数 调用,而非从矢量化循环内进行多个串行化标量函数调用。

在此公开的技术的各种组合还准许函数的矢量化不包含循环,其 与公认知识相反,并且仍提供许多优点。特别地讲,这些技术可以增 加软件应用中的总矢量化的量。

附图说明

图1是例示根据某些实施例的、可操作以实现用于使能实现软件 应用的通用矢量化的技术的计算机系统的框图。

图2是例示根据某些实施例的、在通过计算机系统执行时可以生 成可执行代码的编译器的框图。

图3示出了例示根据某些实施例的、表达依赖关系数据库中的依 赖关系的方法的流程图。

图4示出了例示根据某些实施例的、矢量化函数的方法的流程图。

图5示出了例示根据某些实施例的、全函数矢量化方法的流程图。

图6示出了例示根据某些实施例的、利用所矢量化函数的方法的 流程图。

虽然易受各种修改和另选形式,但在本说明书中讨论的具体实施 例在附图中通过示例进行了示出,并且在此将进行详细描述。然而, 应当明白,附图和详细描述不是旨在将本公开限制成所公开的特定形 式,但与此相反,本发明要覆盖落入如所附权利要求书所限定的本公 开的精神和范围内的所有修改例、等同物以及另选例。

具体实施方式

介绍

下面的说明书首先讨论例示计算机系统或装置。本说明书还描述 了例示编译器,其可以被配置成执行和/或生成用于计算机系统的可执 行代码。接着,本说明书提出了用于使能实现非叶循环和全函数矢量 化的几种技术。

例示性计算机系统

图1描绘了根据某些实施例的、可操作以实现用于使能实现软件 应用的通用矢量化的技术的例示计算机系统。在该非限制例中,计算 机系统100包括经由I/O接口130耦接至存储器120的一个或多个处理器 110a-110n。计算机系统100还包括耦接至I/O接口130的网络接口140和 存储接口150。存储接口150将外部存储装置155连接至I/O接口130。而 且,网络接口140可以将系统100连接至网络(未示出)或者连接至另 一计算机系统(未示出)。

在一些实施例中,计算机系统100可以是仅包括一个处理器110a 的单一处理器系统。在其它实施例中,计算机系统100可以包括两个或 更多个处理器110a-110n。处理器110a-110n可以包括能够执行指令的任 何处理器。例如,处理器110a-110n可以是实现任何合适指令集架构 (ISA)的通用或嵌入式处理器,举例来说,如x86、PowerPCTM、 SPARCTM、或MIPSTMI SAs。在一实施例中,处理器110a-110n可以包 括在美国专利No.7617496和美国专利No.7395419中描述的 Macroscalar处理器的各种特征。

系统存储器120可以被配置成存储可通过处理器110a-110n访问 的指令和数据。例如,系统存储器120可以是静态随机访问存储器 (SRAM)、同步动态RAM(SDRAM)、非易失性/闪速型存储器、或 者任何其它任何合适类型的存储器技术。实现下面详细描述的希望功 能或应用的程序指令和/或数据的一部分可以被示出存储在系统存储 器120内。另外或另选的是,那些程序指令和/或数据的一部分可以被 存储在存储装置155、一个或多个处理器110a-110n内的高速缓冲存储 器中、或者可以经由网络接口140从网络抵达。

I/O接口130可操作以管理处理器110a-110n、系统存储器120以及 该系统中的或接合至其的任何装置(包括网络接口140、存储接口150 或其它外围接口)之间的数据通信。例如,I/O接口130可以将来自一 个组件的数据或控制信号转换成适于供另一组件使用的格式。在一些 实施例中,I/O接口130可以包括对通过各种类型外围总线接合的装置 的支持,举例来说,如外围组件互连(PCI)总线或通用串行总线 (USB)。而且,在一些实施例中,I/O接口130的一些或全部功能可以 并入处理器110a-110n。

网络接口140被配置成允许数据在计算机系统100与接合至网络 的其它装置(举例来说,如其它计算机系统)之间交换。例如,网络 接口140可以支持经由有线或无线通用数据网络、电信/电话网络、诸 如Fibre Channel SAN等的存储区域网络等的通信。

存储接口150被配置成允许计算机系统100与诸如存储装置155的 存储装置接口连接。存储接口150可以支持以下标准存储接口:如高级 技术附加数据包接口(ATAPI)标准(其还可以被称为集成驱动电子 设备(IDE))的一个或多个合适版本、小型计算机系统接口(SCSI) 标准、IEEE 1394“Firewire”标准、USB标准、或者适于互连海量存 储装置与计算机系统100的另一标准或专有接口。例如,存储装置155 可以包括可以固定或可去除的磁性、光学或固态介质。存储装置155 还可以对应于硬盘驱动器或驱动器阵列、CD或DVD驱动器、或者基 于非易失性存储器(例如,闪速存储器)的装置。

系统存储器120和存储装置155表示被配置成存储程序指令和数 据的计算机可访问或计算机可读存储介质的例示性实施例。在其它实 施例中,程序指令和/或数据可以在不同类型的计算机可访问介质上接 收、发送或存储。一般来说,计算机可访问介质或存储介质可以包括 任何类型的海量存储介质或存储器介质,如磁性介质或光学介质。计 算机可访问介质或存储介质还可以包括任何易失性或非易失性介质, 如RAM(例如,SDRAM、DDR SDRAM、RDRAM、SRAM等)、ROM 等,无论作为系统存储器120还是另一类型存储器包括在计算机系统 100中。经由计算机可访问介质存储的程序指令和数据可以通过传输介 质或诸如电信号、电磁信号或数字信号的信号传输,其可以经由诸如 网络和/或无线链路的通信介质来输送,如可以经由网络接口140实现。

典型地讲,计算机系统100可以采取台式或膝上型计算机的形式。 然而,如根据本公开将容易理解,计算机系统100可以是能够执行软件 的任何合适装置。例如,计算机系统100可以是平板计算机、电话等。

例示性编译器

一般而言,编译器可以对应于被配置成将源代码翻译或变换成目 标代码的软件应用(例如,计算机可执行指令中的一个或多个模块), 该源代码可以采用诸如C、C++或任何其它合适编程语言的高级编程 语言来表示。表达源代码的语言可以被称为源代码语言或简称为源语 言。典型地讲,目标代码可以采用适于供目标计算架构处理的指令和 数据的形式来表示,尽管在一些实施例中,可以针对所生成的目标代 码执行附加处理((例如,链接),以将目标代码变换成机器可执行代 码。在各种实施例中,这种附加处理可以通过编译器或者通过分离应 用来执行。

目标代码可以采用机器可读形式(例如,二进制形式)、采用可 需要附加处理以生成机器可读代码的人可读形式(例如,汇编语言), 或者采用人可读形式和机器可读形式的组合来表示。针对目标代码的 目标架构可以和通过处理器110a-11n所实现的、其上编译器被配置成 执行的ISA相同。然而,在某些情况下,编译器可以被配置成生成针 对其上编译器执行(“交叉编译器”)的ISA相比不同的ISA的目标代码。

图2描绘了根据某些实施例的、在通过计算机系统100或另一合适 计算机系统执行时可以生成可执行代码的例示性编译器。编译器200 包括前端220和后端230,其又可以包括优化器240和代码生成器250。 如上所示,前端220接收源代码210,而后端230生成目标代码,举例来 说,如标量目标代码260、矢量化目标代码270或其组合。编译器200 还可以生成与目标代码260和/或270中的一个或多个相关联的依赖关 系数据库280。

虽然源代码210通常采用高级编程语言编写,但源代码210可以另 选地对应于诸如汇编语言的机器级语言。例如,编译器200可以被配置 成应用其最优化技术,以汇编除了采用高级编程语言编写的代码以外 的语言代码。而且,编译器200可以包括前端220的许多不同实例,其 皆被配置成处理采用不同相应语言编写的源代码210,并且生成供后端 230处理的类似中间表示。在这种实施例中,编译器200可以有效地充 任多语言编译器。

在一实施例中,前端220可以被配置成执行源代码210的初步处 理,以确定源在词汇上和/或语法上是否正确,并且执行适于准备源代 码210以供后端230进一步处理的任何变换。例如,前端220可以被配置 成处理存在于源代码210内的任何编译器指令,如可以导致源代码210 的某些部分被包括在编译处理中而其它部分不包括在其中的条件编译 指令。前端220还可以不同地被配置成将源代码210转换成记号 (tokens)(例如,根据由源语言定义的空白和/或其它分隔符),确定 源代码210是否包括禁止为源语言的任何字符或记号,以及确定所得记 号流是否服从按源语言定义的合式表达的语法规则。在不同情形下, 前端220可以被配置成执行这些处理活动的不同组合,可以省略上述特 定动作,或者可以包括不同动作,根据前端220的实现和前端220的目 标源语言,例如,如果源语言未提供用于定义编译器指令的语法,则 前端220可以省略包括扫描用于编译器指令的源代码210的处理动作。

如果前端220在处理源代码210期间遭遇错误,则其可以异常中止 处理并且报告错误(例如,通过将错误信息写入至日志文件或者显示 器)。否则,在充分分析源代码210的语法和语义内容时,前端220可以 向后端230提供源代码210的中间表示。一般而言,该中间表示可以包 括表示源代码210的结构和语义内容的一个或多个数据结构,如语法 树、图表、符号表或其它合适数据结构。该中间表示可以被配置成保 存识别源代码210的语法和语义特征的信息,并且还可以包括通过剖析 和分析源代码210而生成的附加注释信息。例如,该中间表示可以包括 明确地识别源代码210的不同块或段之间的控制关系的控制流程图。这 种控制流程信息可以被后端230采用,以例如确定,可以怎样重新布置 (例如,通过优化器240)源代码210的功能部分,以在保存源代码210 内的必需执行顺序关系的同时改进性能。

后端230通常可以被配置成将中间表示变换成标量代码260、矢量 化代码270、或两者的组合中的一个或多个。具体来说,在所示实施例 中,优化器240可以被配置成试图变换中间表示,以改进所得标量代码 260或矢量化代码270的某一方面。例如,优化器240可以被配置成分析 中间表示以识别存储器或数据依赖关系。在一些实施例中,优化器240 可以被配置成执行各种其它类型的代码优化,如,矢量化、循环优化 (例如,循环融合、循环展开等)、数据流优化(例如,共同子表达式 消除、常量折叠等),或任何其它合适的优化技术。优化器240还可以 被配置成生成依赖关系数据库280。如下更详细描述的,依赖关系数据 库280可以表达对存储器和/或源代码210内的数据依赖关系的指示。另 外或另选的是,连同源代码210的矢量化,依赖关系数据库280可以暴 露与矢量化目标代码270相关联的矢量接口。

代码生成器250可以被配置成处理如通过优化器206变换的中间 表示,以便生成标量代码260、矢量化代码270、或者两类代码的组合。 例如,代码生成器250可以被配置成生成由目标架构的ISA定义的矢量 化机器指令,以使得由实现目标架构的处理器(例如,处理器110a-110n 之一,或者不同处理器)执行所生成指令可以实现由源代码210指定的 功能行为。在一实施例中,代码生成器250还可以被配置成生成与可能 不是源代码210中所固有的操作相对应的指令,但其在优化处理期间可 以由优化器240添加。

在其它实施例中,编译器200可以被分区成与所示那些组件相比 更多、更少或不同的组件。例如,编译器200可以包括链接器(未示出), 其被配置成采取一个或多个目标文件或库作为输入,并且组合它们以 生成单一(通常可执行的)文件。另选的是,该链接器可以是与编译 器200分离的实体。如上所述,编译器200的组件中的任一个,以及所 执行的由此包括下面参照图3-6描述的那些的方法或技术中的任一种, 可以部分地或者整体地被实现为存储在合适计算机可访问存储介质内 的软件代码。

源代码210例如可以表示软件功能或算法。所得目标代码260和/ 或270例如可以是可以被其它函数调用的库或外部函数。下面,对在操 作期间(并且具体地说,在其矢量化操作期间)由编译器200采用的例 示性技术进行更详细讨论。

非叶循环的矢量化

许多现代计算机具有通过并发地执行两个或更多个不同操作来 执行计算工作量的某一类型并行处理的能力。例如,超标量处理器可 以允许计算机尝试一次执行多个独立指令。通称为“矢量计算”(其可 以被视为并行计算的特定情况)的另一技术允许计算机尝试执行一次 对多个数据项操作的单一指令。矢量计算的各种示例可以在该单一指 令、现今在各种处理器中可用的多个数据(SIMD)指令集中找到, 例如,包括用于PowerPCTM处理器的IBM的AltiVecTM和SPETM指令集 扩展,和Intel的MMXTM和SSETM指令集扩展的变型。这种SIMD指令 是可以被矢量化编译器作为目标的矢量指令的示例,尽管其它类型的 矢量指令或运算(包括可变长度矢量运算、预测(predicated)矢量运 算、对矢量和标量/立即(immediates)的组合操作的矢量运算)也是 可能并且可以设想的。

一般而言,将源代码变换成矢量化目标代码的处理可以被称为 “矢量化”。当利用编译器执行时(例如,与手工矢量化源代码相反), 可以将矢量化称为“编译器自动矢量化”。自动矢量化的一个特定类型 是循环自动矢量化。循环自动矢量化可以将在多个数据项上迭代的过 程循环转换成能够在分离处理单元(例如,图1中的计算机系统100的 处理器110a-110n,或者处理器内的分离功能单元)内并发地处理多个 数据项的代码。例如,为将两个数字阵列A[]和B[]相加在一起,过程 循环可以通过这些阵列迭代,在每一个迭代期间将一对阵列组元相加。 当编译这种循环时,矢量化编译器可以利用目标处理器实现能够并发 地处理数量固定或可变的矢量组元的矢量运算的事实。例如,编译器 可以自动矢量化阵列相加循环,以使得在每一个迭代,并发地相加阵 列A[]和B[]的多个组元,缩减为完成该相加所需的迭代数。典型程序 在这种循环内花费其显著量的执行时间。这样,自动矢量化循环可以 产生性能改进而不需要程序员干涉。

在一些实施例中,编译器自动矢量化受限于叶循环,即,不对其 它函数进行调用的循环。非叶循环的矢量化(即,对其它函数进行调 用的那些)因外部函数调用的副作用通常不清楚(opaque)而通常非 常困难,尤其是在它们的源代码不可用于过程间分析时候,举例来说, 如利用库的情况。出于例示的目的,考虑下面的循环:

为矢量化该循环,编译器200可以确定函数foo()是否与阵列A[]交 互(例如,读取或写入)。这里,存在三种可能性:(1)函数foo()与 A[]不交互;(2)函数foo()与A[]不交互;或者(3)函数foo()可能与A[] 交互(例如,根据编译时间或运行时间条件,foo()可以与A[]交互或者 可以不交互)。其中函数foo()可能与A[]交互的情况存在和其中函数foo() 事实上的确与A[]交互的情况相似的问题。在foo()与A[]不交互的情况 下,那么下面的可矢量化代码等效于上面的循环:

该示例示出了,在矢量化非叶循环的处理中,编译器200受益于 获知函数所访问的存储器和/或该存储器是否被读取和/或写入。因为 主要循环通常包含它们之内的函数调用,所以非叶循环和由它们调用 的函数的矢量化被优选用于高程度的矢量化。为使能实现该级别的矢 量化,在此描述的技术和系统的各种实施例增加了跨先前已经编译的 库和模块的依赖关系和潜在依赖关系的编译时间可见性。例如,该信 息在编译调用函数时可以是可用的,与初始地编译该库或模块的时间 (或位置)无关。因此,在此描述的某些技术建立例示性编译器基础 结构,以创建该可见性并探索通过其使能实现的矢量化的类型。

依赖关系数据库

当编译调用外部函数的代码时,可以希望的是,确定外部函数的 接口(例如,外部函数采取的参数的数量和/或类型,和/或其返回的 结果的数量和/或类型)。例如,这种接口信息可以在确定调用代码是 否已经正确地实现该外部函数时有用。外部可调用函数通常可以在首 部文件中暴露它们的接口定义。然而,这种首部文件可以不暴露不是 外部函数的用于调用函数的接口的一部分的变量的细节,但其仍然可 以影响代码矢量化。例如,在上面例示的循环中,for循环的矢量化可 以取决于函数foo()与阵列A[]怎样交互。然而,因为foo()不采取A[]作 为一参数,所以与foo()相对应的首部文件可能不足以指示针对编译器 200的这种依赖关系。

在此还可以被称为“永久依赖关系数据库”的依赖关系数据库可 以在库中描述外部可调用函数的依赖关系。即,依赖关系数据库可以 向调用函数暴露单独从被调用函数的接口不一定明显的被调用函数的 各种依赖关系。该数据库在编译调用库的函数时可以被访问。一般而 言,依赖关系数据库可以永久地存储对可调用代码的依赖关系的指示, 以使依赖关系跨编译器调用可见。例如,在一些实施例中,依赖关系 数据库可以被实现为包括指示各种依赖关系的人可读和/或机器可读 内容的依赖关系文件(与首部文件类似)。在其它实施例中,依赖关系 数据库可以利用其它技术来实现,如利用基于表的关系数据库、半结 构化数据(例如,利用可扩展标记语言(XML)格式化),或者任何 其它合适的技术。为简化说明,下面的讨论引用采用依赖关系文件的 实施例。然而,应注意到,这仅是依赖关系数据库的一非限制例。

在一实施例中,编译器200在包含对应首部文件(例如,stdlib.h) 时自动访问依赖关系文件(若存在的话)。这种机制可以允许矢量化编 译器,举例来说,如用于在具有获知外部库的依赖关系的优点的同时 编译现有代码而不需要修改的Macroscalar编译器。接着,编译器200 可以在编译库时自动地生成依赖关系文件。

包含在依赖关系文件中的信息可以形成应用编译器接口(ACI), 其提供编译器200可以用于理解函数的约束条件的信息。具体来说,依 赖关系文件可以表达有关通常不在调用函数的范围内的变量的信息。 例如,依赖关系文件中表达的变量可以包括不是被调用函数的参数的 数据项(即,这种变量可能不由被调用函数的编程接口定义为该被调 用函数的参数)。通过使用依赖关系文件,调用函数例如可以变为获知 被调用函数是读取还是写入函数静态或文件静态变量。依赖关系文件 还可以允许编译器200在共享同一名称但具有不同范围的变量之间区 分。

作为一非限制例,当编译库stdlib时,编译器通常仅生成目标文件 stdlib.o。利用在此描述的技术,编译器200还可以例如在编译时间生成 依赖关系文件stdlib.d。依赖关系文件stdlib.d暴露与stdlib.h中定义的公 用函数相关联的存储器依赖关系。包括来自其源代码的stdlib.h的其它 程序可以触发编译器200搜索对应位置中的关联的依赖关系文件 stdlib.d。该依赖关系文件可以与stdlib.h和stdlib.o一起分布和安装。在 一个实现中,不存在依赖关系文件将意指,没有可用的有关库的附加 信息,其可能是针对传统库的默认状态,并且不会导致任何编译错误。

依赖关系数据库可以使能通过按在编译调用库函数的代码时可 见于编译器200的方式暴露先前编译的库函数(或程序中的任何函数) 的数据依赖关系特征来矢量化非叶循环。该信息可以被使得在不需要 展现针对该库的源代码的情况下可用。

在一些实施例中,依赖关系信息可以按库的编译时间生成。例如, 针对编译的每一个函数,编译器200可以注释针对传递到被编译函数中 的函数静态变量、文件静态变量、全局变量、以及/或指针的访问类型。 接着,编译器200可以记录哪些符号被读取或写入,并且采用可以按引 用库的其它代码的编译时间访问和使用依赖关系文件的形式来输出该 信息。

作为另一非限制例,如果函数foo()在文件foo.c中定义,并且其接 口在首部文件foo.h中定义,则在foo.c的编译时间,函数foo()的存储器 依赖关系特征可以被存储到依赖关系文件foo.hd中(应注意到,可以 采用用于依赖关系文件的任何合适命名约定)。使用函数foo()的调用函 数可以包括首部文件foo.h,但可以不必访问文件foo.c。在在编译调用 函数期间引用foo.h时,编译器200可以自动地搜索依赖关系文件 foo.hd,以查看其是否存在。因为依赖关系文件foo.hd的存在性是可选 的,所以缺乏这种文件可以暗示在文件foo.h中定义的函数的依赖关系 特征未知,由此建议编译器200在矢量化调用函数时应当进行不利假 定。然而,如果依赖关系文件存在,则编译器200可以使用该文件中的 依赖关系信息,以在矢量化调用函数期间,利用包含在其中的依赖关 系特征来进行更准确且主动的假定。

参照图3,根据某些实施例,对表示表达依赖关系文件中的依赖 关系的方法的流程图进行描绘。在框300中,编译器200接收要编译的 函数。例如,编译器200可以在处理用于编译的源代码时(如在编译包 括该函数的库期间)接收函数。在框310中,编译器200分析该函数并 且识别该函数内的所表达的依赖关系。该所表达的依赖关系例如可以 是存储器或者与不是被调用函数的参数的数据项相关联的数据依赖关 系。更一般地说,与函数对特定数据项的所表达的依赖关系可以指示 该函数是仅读取该特定数据项,仅写入该特定数据项,还是既读取又 写入该特定数据项。在各种实施例中,分析函数可以包括诸如执行对 函数的词汇、语法、以及/或语义分析的动作。分析还可以包括生成指 示被编译代码的操作和/或数据引用的某一方面的剖析树、符号表、中 间代码表示、以及/或任何其它合适数据结构或表示。

在框320中,编译器200将对所表达依赖关系的指示存储在与该函 数相关联的依赖关系数据库中。例如,在分析该函数期间,编译器200 可以识别被该函数使用的、对于该函数来说不必是本地或专用的、并 由此能够被该函数外部的代码读取或写入的变量。这种变量可以是编 译器200可能识别的所表达的依赖关系的示例,并且编译器200可以将 对这些变量的指示存储在依赖关系数据库内(应注意到,在一些实施 例中,编译器200还可以识别和指示对于该函数来说是本地或专用的依 赖关系)。在各种实施例中,对所表达的依赖关系的指示可以包括识别 所表达的依赖关系的信息,如变量所依赖的名称。该指示还可以包括 特征化所表达的依赖关系的信息,如有关函数是读取还是写入变量的 信息,以及/或有关变量的数据类型或范围的信息(例如,该变量是否 为全局的、专用的、静态的等)。如根据本公开将容易明白,依赖关系 文件可以按任何合适格式(举例来说,如可扩展标记语言(XML)等) 来创建或更新。而且,在一些实施例中,依赖关系可以代替肯定方式 按否定方式来指示,或者除了肯定方式以外按否定方式来指示。例如, 依赖关系文件可以明确地指示,除了或代替指示不存在的那些所表达 的依赖关系,给定变量不取决于外部代码。

例如,考虑下面的示例,在要编译func1.c的情况下:

在这种情况下,func1.c对下面所示的外部函数foo1.c进行调用:

仅出于例示性目的,再现针对被调用函数foo1.c的源代码。应当 明白,只要存在用于foo1.c的依赖关系数据库(在这个示例中,依赖关 系文件),其源代码在编译调用函数func1.c期间就不需要可用。在这 个示例中,存储在依赖关系文件foo1.hd中的所表达的依赖关系信息 (其已经在编译文件foo1.c时的时刻生成)可以表达函数静态变量“e” 被读取和写入的事实。这样,下面示出了对应依赖关系文件的一个非 限制例:

在文件func1.c的编译时间,包含首部文件foo1.h可以导致依赖关 系文件foo1.hd被编译器200读取。该信息向编译器通知被调用函数 foo1()的所表达的依赖关系:即,该静态变量“e”被读取和写入。这还 允许编译器200检测,即使它们在调用函数func1()中使用,全局变量 “A”和“F”也不被被调用函数foo1()引用。这种获知允许编译器200矢量 化函数func1()中的循环,因为其可以确定并行操作不会导致不正确操 作。在这种情况下,func1()中的循环将针对被处理矢量中的每一个组 元调用foo1()一次。

如果函数foo1()被写入全局“A”,则编译器200可能不矢量化 func1()中的循环,或者其可以使用该信息以仅矢量化该函数的一部 分。在这种情况下,编译器例如可以串行化针对函数foo1()的调用并且 存储器引用至“A”,同时允许该循环的其余部分按并行方式执行。

参照图4,描绘了表示矢量化函数的方法的一实施例的流程图。 在框400中,编译器200识别调用函数。在一非限制实施例中,调用函 数可以包括非叶循环,在该情况下,调用函数可以包括针对外部或被 调用函数的调用。参照刚才给出的代码例,编译器200可以处理func1.c 源代码,并且识别func1()函数作为包括调用foo1()函数的非叶for循环 的调用函数。

在框410中,编译器200可以尝试访问与被调用函数相关联的依赖 关系数据库。在某些情况下,依赖关系数据库(例如,依赖关系文件) 例如可以经由命令行参数、嵌入源代码内的编译器指令、或者经由另 一合适技术,明确地指示给编译器200。在其它情况下,编译器200可 以根据命名约定,尝试从其它数据推断依赖关系文件的名称。例如, 如果首部文件被包括在源代码中,则编译器200可以搜索从首部文件的 名称导出的依赖关系文件。在一些实施例中,编译器200可以基于被调 用函数的名称来搜索依赖关系文件。

如果依赖关系数据库存在,则其可以指示被调用函数内的所表达 的依赖关系。该所表达的依赖关系例如可以是与不是被调用函数的参 数的数据项相关联的存储器或数据依赖关系,如上所述。在某些情况 下,编译器200可以检查许多不同命名约定以确定是否存在依赖关系文 件。

在框420中,接着,编译器200至少部分地基于所表达的依赖关系 (或者缺乏依赖关系),确定调用函数是否与被调用函数交互。例如, 在访问与函数foo1()相关联的依赖关系文件时,编译器200可以确定 foo1()取决于变量“e”而非变量“A”或“F”。由此,至少关于变量“e',编 译器200可以确定调用函数func1()不与被调用函数foo1()交互。

在框430中,根据对调用函数是否与被调用函数交互的确定,编 译器200可以确定是否矢量化调用函数的至少一部分。例如,基于上述 所表达的依赖关系信息,编译器200可以通过生成对多个数据项(例如, 阵列组元)和/或多个循环迭代并发地操作的矢量代码,来尝试矢量化 调用函数func1()。

在各种实施例中,依赖关系数据库可以表达各种类型的信息,该 信息可以在确定是否矢量化函数时对编译器200有用。示例包括跟踪对 以下项的读取和写入:数据对象、指针、被指向数据对象、被指向对 象内的已知偏移、进入被指向对象的未知偏移(其可以有效地构成针 对整个对象的引用)、对象内的可变偏移(被指向对象和数据对象两者, 其可以利用所讨论变量使能实现运行时间依赖关系分析)、以及进入进 入更高级对象中的未知偏移的对象中的已知偏移(例如,在引用未知 数量的已知偏移但其它偏移仍保持未引用时)。

已知偏移信息可以使得编译器200能够矢量化,而不生成附加依 赖关系检查指令,同时可变偏移信息可以被用于在运行时间生成分析 变量依赖关系的依赖关系检查指令,其可以允许实现增加的矢量并行 性,同时保持程序正确性。

如上说明的,依赖关系数据库可以表达有关被调用函数的信息, 该信息在矢量化调用函数时有用于编译器200。在这点上,依赖关系数 据库可以存储这样的信息:诸如存储器访问类型、寻址模式、以及/ 或附加限定符。

在一些实施例中,通过一函数访问的存储器通常落入两种类型 中:读取和写入。由此,如在上面给出的示例中所示,依赖关系数据 库可以明确地存储对数据项是被读取还是写入的指示。

寻址模式描述了如调用函数所观看的调用函数内的存储器访问。 一些实施例可以定义三种寻址模式:常数、变量、以及未知,虽然另 选实施例也是可能并且可以设想的。这三个寻址模式中的每一个可以 分别根据寻址是否可以通过编译器在编译时间建立、根据运行时间的 调用函数、或者根据运行时间的被调用函数来确定。另外,一些实施 例可以定义针对寻址模式的两个正交限定符:公用和专用。这些指定 所关联的变量是否可见于外部模块。

根据一些实施例,常数寻址描述了可以在编译时间从模块外部分 解的寻址。这包括针对命名变量、命名结构内的命名结构组元、或者 可以在编译时间分解的阵列索引。例如,g(命名变量)、str.g(命名 结构内的命名结构组元)、h[5](根据常数索引的阵列)、以及str[5].h (根据常数索引的命名结构阵列内的命名结构组元)表示常数寻址的 示例。这些示例可以表示静态变量或全局变量。(自动存储通常是临时 的(例如,在进入模块时分配和在模块退出时解除分配),并由此通常 在模块外部可见)。下面的示例例示了针对使用常数寻址的函数的依赖 关系:

在一些实施例中,变量寻址描述了不是恒定的而且也不被被调用 函数修改的寻址。因此,其可以在运行时间通过调用函数评估。示例 包括对其中可以通过调用函数观察寻址的被指向对象和阵列的引用。 考虑下面的函数:

该函数将输出针对依赖关系文件的下列依赖关系,声明函数写入 A[g]和读取A[x](都是可变寻址阵列):

在这个示例中,如果函数assignA()每一调用函数迭代仅被调用一 次,则依赖关系检查(其还可以被称为意外(hazard)检查)可能不 必要。被调用函数assignA()可以确定g和x是否交叠,并因此例如利用 Macroscalar技术来区分矢量。

考虑其中外部循环每一迭代调用assignA()两次的情形:

尽管意外可能存在于g1与x之间,或者g2与y之间,但这些依赖关 系与函数的单一调用有关。在该特定实例中,调用循环可以仅检查g1 与y以及g2与x之间的潜在意外,其可以根据依赖关系文件中的信息来 识别。

在一些实施例中,未知寻址类似于如上所述的变量寻址,但通常 应用至其中运行时间寻址不能通过调用函数评估的情形。这例如可以 发生在其中被调用函数利用来自依赖关系文件的信息按不可见于调用 函数的方式修改地址变量的值的情形中。

附加限定符“公用”和“专用”可以指定链接器是否输出一符号以 允许通过调用函数检查变量。例如,针对接下来至上面给出的最后示 例中的A[]的引用被指定“专用”,因为A[]被声明为不向调用assignA() 的函数输出的文件静态变量。在这个示例中,编译器200可以根据依赖 关系信息确定assignA()函数怎样寻址A[],但不能够生成实际上读取A[] 的值的代码。

全函数矢量化

如上详细描述的,可以采用编译器自动矢量化,以按可以透明于 程序员或其它用户的方式,根据非矢量化源代码来生成矢量化代码。 这种编译器自动矢量化可以使得源代码能够在很少或没有程序员干涉 的情况下利用由矢量计算硬件提供的性能改进的优点。

然而,如果非叶函数(即,调用其它函数的函数)要有效地矢量 化,则可以希望提供向调用函数暴露矢量接口而非可能在原始源代码 中表示的标量接口的被调用函数的形式。

而且,应用开发者可能希望以针对多种计算平台的应用为目标, 并不是所有这些计算平台都可以提供矢量资源。例如,处理器家族的 移动版可能省略矢量运算以缩减芯片尺寸和功耗,而同一处理器家族 的台式版可能被开发成与功耗相比更注重处理功率。在这种情形下, 为了在移动处理器上执行,应用可能需要仅利用标量函数来编译,而 该应用当在台式处理器上执行时,可能使用标量函数或矢量函数。然 而,关于上述自动矢量化,可以希望允许该应用在矢量平台和非矢量 平台上有效执行,同时缩减或消除程序员干涉。

对应地,在矢量化一函数时,根据在此描述的一些实施例的编译 器可以根据单一源代码描述生成函数的标量和矢量版本。该函数例如 可以是库函数,尽管更一般地说,其可以对应于任何可调用过程或方 法。在一些实施例中,函数的标量版本可以使用如由源代码最初指定 的标量接口。同时,该函数的矢量版本可以实现针对该函数的矢量接 口,接受矢量参数和/或生成矢量返回值。通过生成该函数的标量和矢 量版本两者,编译器可以使得代码能够在编译时间或者运行时间更灵 活地为可用资源特制。而且,通过生成被调用函数的矢量化版本并将 所得矢量接口暴露给调用函数,编译器可以辅助调用函数的矢量化, 由此传播用于从叶函数向上分级地矢量化的机会。

矢量接口例如可以在与该函数相关联的依赖关系数据库(如依赖 关系文件)中表达。例如,考虑下面的函数外壳,其中,该函数的内 部细节已经省略:

针对该函数的标量接口可以表示(例如,在依赖关系文件内)为:

int foo(int A)

该表示反映了,根据该版本,foo()采取标量参数并且返回标量结 果。

同一函数在被矢量化以例如一次在多个数据项上执行操作时可 以变为:

这样,针对该函数的矢量接口可以被表示(例如,在依赖关系文 件内)为:

Vector foo(Vector A)

不同于现有表示,该表示指示foo()的该版本采取矢量参数并且返 回矢量结果。

参照图5,描绘了表示全函数矢量化方法的一实施例的流程图。 在框500中,编译器200接收要编译的函数。在框510中,编译器200可 以编译该函数的标量形式。在框520中,编译器200可以编译该函数的 矢量形式。而在框530中,编译器200可以在依赖关系数据库中表达与 该函数的矢量版本相关联的矢量接口。

这种另选矢量接口的存在允许编译器200从矢量化循环内进行矢 量函数调用,而非从矢量化循环内进行多个串行化标量函数调用。例 如,考虑针对外部函数foo()进行调用的调用函数内的下列循环:

如果foo()仅具有标量接口,则用于矢量化该循环的机会可能受 限,例如,限制矢量化的指配。然而,存在foo()的矢量版本可以增加 对于循环矢量化的机会。例如,上述循环的矢量化版本可能利用矢量 参数调用foo()并且可能接收矢量结果,使能实现更多并发执行并缩减 循环内的串行化。而且,不同于先前方法,该技术准许函数的矢量化 不包含循环。这可以增加应用中的总矢量化的量。

函数的两个版本中的循环可以被矢量化。一般而言,“水平”矢量 化可以涉及这样类型的矢量化,即,其中,循环的迭代被映射成矢量 的对应组元。“垂直”矢量化可以涉及这样类型的矢量化,即,其中, 循环的迭代性质可以保留(即,与被映射至如在水平矢量化中的矢量 组元相反),但其中标量变量用矢量变量替换,使得每一个迭代在比代 码的标量版本更多的数据上并发地操作。

函数的标量版本中的循环可以利用Macroscalar技术水平地矢量 化,而该函数的矢量版本中的循环可以水平地或者垂直地矢量化。这 可以增加应用中用于矢量化的机会。除了矢量化函数调用的性能和效 率益处以外,该技术还可以增加一应用中垂直地矢量化的循环的数量, 由此缩减在水平地矢量化循环时导致的开销。

参照图6,描绘了表示利用矢量化函数的方法的一实施例的流程 图。在框600中,编译器200识别针对被调用函数进行调用的调用函数。 例如,调用函数可以包括针对预编译库内的一函数进行调用的循环。 在框610中,编译器200访问与被调用函数相关联的依赖关系数据库。 在框620中,编译器200检查依赖关系数据库,以确定被调用函数的矢 量变型是否可用。在一个实现中,如果该矢量版本可用,则在框630 中,编译器200编译调用函数,以利用被调用函数的矢量变型。如果该 矢量版本不可用,则编译器200编译调用函数,以利用标量版本(例如, 通过迭代地调用该函数的标量版本)。

例如,再次考虑下面的循环:

当矢量化该循环时,编译器可以检查与foo()相关联的依赖关系数 据库,以确定与foo()相关联的矢量接口是否存在。如果foo()的矢量接 口不存在,则编译器200可以仅局部地矢量化该循环,例如,在按标量 格式留下函数调用的同时通过矢量化该指配来进行。

另一方面,如果foo()具有在其依赖关系数据库中表达的矢量化接 口,则在一些实例中,编译器200可以整体矢量化该循环(例如,通过 替换或以其它方式将该指配和函数调用变换成矢量运算)。

当编译器检查foo()的依赖关系数据库,以确定是否存在用于被调 用函数的矢量化接口时,编译器可以另外或者另选地检查与被调用函 数(可以被表达为与foo()相关联的同一(或另一)依赖关系数据库) 相关联的任何存储器依赖关系。

在一些实现中,可以独立地跟踪针对一阵列的每一维的寻址,以 最小化不确定性。这种构思通常可以应用至所有聚合数据类型,如结 构和阵列。下列的示例更详细地例示了编译器(例如,如编译器200) 怎样可以使用依赖关系数据库信息以使能矢量化,以及怎样可以在可 能的时候采用函数的矢量版本来代替标量版本(应注意到,在其它实 施例中,依赖关系数据库可以被使用而与确定是否存在矢量函数接口 无关,反之亦然)。

在这个示例中,函数bar()将输出指示其写入p.ptr[]并且从p.b和j 读取的依赖关系(例如,在编译函数bar()时由编译器200生成的依赖 关系文件,如上所述):

应注意到,在这种特定情况下,可以不必识别参数的引用为“公 共”或“私有”。而且,可以不必声明从p或j读取的函数,因为至少在该 示例中可以假定函数使用其自身参数。可以在依赖关系数据库中包括 myStruct的类型定义,以将其暴露至调用foo()的函数,但可以不必通 过首部文件包含来暴露至myStruct的定义。

在编译期间,编译器200可以编译函数bar()而不需要矢量化其, 因为不存在通过其矢量化的循环。这样做,其可以生成具有下列接口 的bar()的标量版本:

int bar(myStruct*p,int j)

在这个示例中,bar()可以采取针对一结构的指针的单一实例和单 一整数作为参数,并且返回单一整数作为结果。由此,这种版本的bar() 在其输入和输出方面是标量。

然而,编译器200还可以利用还可以在依赖关系数据库中输出的 下列接口来编译矢量函数:

Vector bar(Vector p,Vector j,Vector pred)

在这个示例中,预测矢量pred指定哪些矢量组元应当通过该函数 来处理。例如,假定矢量包括限定数量的组元,预测矢量可以包含具 有相同限定数量比特的矢量,每一个比特对应于相应组元。每一个比 特可以用作确定其对应矢量组元是否应当被处理的布尔(Boolean)预 测(例如,如果预测比特为“1”,则“是”,而如果其为“0”,则“否”, 反之亦然)。预测允许调用函数进行条件化函数调用,并且如果其未在 矢量长度边界上终止则注意该循环的尾部。应注意到,其它实施例可 以采用不同类型的预测格式,如非布尔预测。

而且,在这个示例中,矢量p是针对结构的指针的矢量,尽管在 这个示例中,它们全部指向同一实例。矢量j是简单整数的矢量。编译 器可以根据标量函数声明来推断该类型信息。

函数bar()的一种可能矢量变型计算针对输入矢量的每一个组元 的p.b+j,并将这些结果写入到p.ptr的恰当阵列索引中。还基于p.b和j 的比较来返回结果的矢量。在该具体示例中,编译器垂直地矢量化该 函数。即,因为bar()不包含循环,所以不存在要变换成矢量组元的循 环迭代,如在水平矢量化中的情况。相反的是,矢量化版本的bar()可 以在矢量输入的不同组元上并发操作。

在编译foo()期间,编译器200可以读取有关函数bar()的依赖关系 信息信息,其可能不必位于同一源文件中,并且即使调用函数正在将 一指针传递至结构g,也确定被调用函数bar()没有针对g.a的依赖关系。 因为其具有这种信息,所以编译器200可以水平地矢量化函数foo()中的 循环。而且,编译器200可以针对每一个所处理的矢量对bar()的矢量 变型进行单一函数调用,而非在该循环的每一个迭代中调用标量变型。 最后,编译器200可以创建具有矢量接口的foo()的矢量变型。在这种特 定情况下,因不能针对依赖关系对x的整个范围进行分析,所以垂直矢 量化可能不能应用。可以应用循环的水平矢量化,并且其被包含在另 一循环内,该另一循环在被传递至函数foo()的矢量变型的矢量组元上 迭代。

在这些假定下,函数foo()可能输出下列依赖关系:

(符号表示未知寻址)。因为函数bar()输出依赖关系“write p.ptr[p.b+j]”,所以编译器200可以断定结构成员ptr[]被写入为x的函 数。由此,编译器200可以向foo()的调用方报告写入的索引未知,因为 其不能通过foo()的调用方来确定。

附加实现技术

这部分描述了附加非限制编译器技术,其可以被用于实现非叶和 全函数矢量化。下面的描述基于Macroscalar编译器技术,但本领域普 通技术人员鉴于本公开将认识到,可以使用其它编译器技术。

先前的实例例示了寻址可以包括数学表达式。这种通常是真实的 只要该表达式不涉及函数调用,并且仅包含对于调用函数可见的项。 这可以包括间接寻址,如当在计算进入其它阵列的索引时使用的查寻 表。

间接寻址是这样一种情形,其中配置编译器和链接器以输出为公 共的静态阵列可以帮助矢量化更多循环。考虑下列示例:

针对foo()生成的依赖关系可以根据编译器和链接器是否被配置 成公共地输出静态符号而不同。在下面的示例中,第一依赖关系文件 表达专用静态变量,而第二依赖关系文件表达公共静态变量:

应注意到,类型声明A在其公开输出时在依赖关系文件中是必需 的。当静态变量专用时,对B[]的寻址是未知的,因为其不能从该函数 外部确定。因为意外检查不可能,所以可以不执行bar()中的循环的矢 量化。然而,在将这些工具配置成公开输出静态变量时,编译器可以 发射读取A[x]的内容的指令,并且检查B[A[x]]与B[x]之间的意外,由 此使能实现循环的矢量化。

自然地,当静态变量被公开输出并外部地寻址时,针对名称冲突 的机会上升。为帮助避免这种冲突,静态变量可以利用它们被声明的 函数和文件来进行名字重整。

一些意外涉及条件化地发生的存储器操作,或者涉及可以基于条 件化计算而不同的寻址。为支持调用涉及条件化依赖关系的函数的循 环的矢量化,可以提供机制来表达该条件怎样影响依赖关系。

例如,考虑下面的代码:

if(A[x]<c)d=B[x];

该代码可以在依赖关系数据库中表达为:

条件化表达还可以存在于计算地址时。例如,考虑下面的代码:

该代码可以在依赖关系文件中表达为:

另选的是,上面稍后的条件化表达可以被表达为:

read public B[A[x]<c?x:x+c];

在某些情况下,未知可以潜入依赖关系表达式。在这种情况下, 一个例示性示例可以是:

A[x]<c?read public B[x]:read public B[];

如果条件为真,则该表达式可以向编译器通知有关B的特定依赖 关系,而如果该条件为假,则通知有关B的未知依赖关系。

潜入条件化表达的未知可以导致表现得好像该条件既为真又为 假的非条件化依赖关系。例如:

A[x]<B[]?read public f:read public g;

可以表达为:

read public f;

read public g;

并且:

read public A[x>?x:x+y];

可以表达为:

read public A[x];

read public A[x+y];

因为调用函数通常不能够评估未知条件,所以它们可以进行保守 假定,即,访问进入A[]的两种可能索引。

在一些实现中,还可以在依赖关系数据库中表达循环依赖关系。 例如,考虑下面的函数:

if(A[x]>b)b=A[x]

在一个实现中,该函数可以表达为:

read public A[x];

read public b;

A[x]>b?write public b;

其中,将指针或引用传递至一函数(还被称为“按引用来传递”), 针对该函数,可以修改其调用参数。这例如与对按值传递的参数的修 改不同,因为按引用来传递的参数的修改可以影响调用函数的操作。 按引用来传递的参数的修改可以按记录静态和全局存储的修改的相同 方式来记录。按值传递的参数的修改可以被处理为局部自动存储的修 改。在某些情况下,因它们对于调用函数不可见,所以可以不记录它 们。

在一些实现中,满足一组标准的函数可以在其中软件推测将需要 矢量化调用循环的情况下推测地调用。因此,推测安全指示符可以在 依赖关系文件中表达,并且可以用作对应代码可以按推测性方式安全 地调用的指示。在一个非限制例中,能够被推测性地调用的矢量函数 可以落入两种类型之一:type-A和type-B。Type-A函数可以是具有在 此描述的正常矢量接口的矢量函数。例如,如果Type-A函数满足下面 的标准,则它们可没有有害副作用地被推测性地调用。首先,该函数 除了本地自动非阵列存储装置以外不访问其它存储器。第二,该函数 不调用不是type-A函数的任何其它函数。type-A函数的示例可能是先 验的或者是其它迭代式收敛算法。

除了由源代码指定的任何返回值以外,type-B函数可以返回指示 那些组元被处理的预测矢量。在一实施例中,用于推测性地调用type-B 函数的标准可以如下。首先,来自非本地存储装置或本地阵列存储装 置的任何读取使用第一故障读取指令。第二,该函数不写入非本地存 储装置或静态本地存储装置。第三,该函数不调用也不是type-A或 type-B函数的任何函数。

从一循环调用type-A函数可以与调用非推测函数相似。典型地 讲,在推测性地调用type-A函数时在调用循环的部分上不需要非特定 动作。然而,调用type-B函数可能需要该调用循环检查返回矢量,以 便确定处理了哪些组元,并且响应地调节调用循环的行为。

诸如编译器200的编译器可以选择成使type-B矢量函数的所有调 用方调节它们的行为,以适应实际上处理了的组元的数量,而不管软 件推测是否在调用循环中使用。另选的是,编译器200可以针对每一个 type-B函数创建两个矢量函数;一个推测性的和一个非推测性的。针 对type-B循环的标准通常可以被指定成确保合格的那些循环很少且较 小,并由此针对该方法的代码尺寸影响可以忽略。

Type-A和type-B矢量函数可以在依赖关系数据库中由它们的声 明来识别,如下所示。在一个实现中,缺乏指定符暗示函数可能不被 推测性地调用。

混叠有时可能是用于矢量化编译器的问题。虽然Macroscalar架 构致力于通过运行时间混叠分析来解决该问题,但存在针对该方法的 开销。Macroscalar程序中的开销贡献于Amdahl’s law中的串行组件, 其可以限制更宽矢量的益处。而且,伴随外部或静态变量的混叠可以 影响横跨函数调用的行为。因此,在一个实现中,执行编译时间混叠 分析,并且将混叠指示符输出至依赖关系文件中。

例如,一种方法可以是将混叠事件分离成两种类别,举例来说, 如入站和出站混叠。从被调用函数的观点来看,入站混叠可以涉及进 入一函数的地址,如从外部变量读取的、或者通过采取外部变量的地 址由该函数计算的作为参数传递的那些。同时,出站混叠可以涉及函 数放出的指针。这些可以是返回值(即,该函数写入外部变量或非引 用指针的值)。

而且,可以跟踪至少两类混叠。“副本混叠(Copies aliasing)” 可以指示该指针可以是另一指针的副本并且可以混叠该指针可以混叠 的任何事物。“指针混叠”可以指示一指针可能影响另一变量。依赖关 系文件中的混叠信息是可能存在混叠的肯定表达。例如,在编译器因 缺乏信息而不能简单地断定两个指针是否引用同一存储器时,不需要 使用其。

声明针对变量的混叠可以类似于声明针对返回值的混叠。例如, 考虑下面的函数:

在一个实现中,该函数可以表达下列依赖关系:

为清楚起见,前述在指针与副本之间区别,尽管其可以按另选语 法组合这两个构思。关于其它依赖关系信息,混叠信息通常通过调用 函数链向上传播。

通过一函数返回的值也可以导致混叠,例如,通过返回值本身, 或者通过因修改按引用传递的变量而返回的信息。这些还可以在依赖 关系文件中进行跟踪。例如,考虑下面的函数:

在一个实现中,该函数可以输出下列依赖关系:

依赖关系声明可以向调用循环通知由foo()返回的指针可能是被 传递的指针的副本。这允许调用循环采取措施,以确保该循环的正确 操作,而不管出现的混叠。而且,该获知还可以使得编译器能够在面 临非ANSI-C兼容的代码时更好地杠杆化ANSI混叠规则。

作为另一考虑,指针的投影可以影响地址计算。例如,考虑下面 的函数:

在一个实现中,该函数可以输出下列依赖关系:

经由函数指针的调用通常因其在编译时间不知道将调用什么函 数或者被调用函数是否支持矢量接口的事实而不能被矢量化。经由指 针调用其它函数的函数可以不输出依赖关系信息,其可以是有关被指 向函数的依赖关系的不确定性的反映。这可以导致编译器将这种函数 视为具有未知依赖关系的标量函数。

在一个实现中,版本化方案允许依赖关系在任何时间点利用最佳 实践来表达。例如,实施例可以准许与由较旧编译器生成的依赖关系 文件向后兼容,而另一实施例可以准许使得较旧编译器也能够读取由 较新编译器生成的文件的双向兼容性。在向后兼容性是唯一需求的情 况下,针对依赖关系文件的版本指示符被用于向较旧编译器通知给定 文件不可读并且应当被忽略。

双向兼容性可如下实现。例如假定编译器版本1不支持阵列索引 中的计算,而编译器版本2支持。A write to B[x+y],可以由版本1编译 器表达为:

另一方面,版本2编译器可以另外利用版本2语法输出同一函数:

利用该方法,不仅版本2编译器可以读取版本1文件,而且其还可 以允许版本2声明无视版本1声明。版本1编译器将获知忽略大于版本1 的任何声明,向其赋予其能够理解的尽可能多的依赖关系信息。随着 编译器技术的成熟这是一种重要能力。

一般而言,如果开发方被要求针对软件进行改变以使能矢量化, 则相对较少的代码可以发生矢量化。为致力于解决这种问题,在此描 述的技术提供了用于执行大规模矢量化的能力,而不需要开发方修改 它们的源代码。

尽管上述实施例已经相当详细地进行了描述,但一旦完全清楚本 说明书,本领域技术人员将明白许多变型例和修改例。下面的权利要 求书旨在被解释成涵盖所有这种变型例和修改例。

去获取专利,查看全文>

相似文献

  • 专利
  • 中文文献
  • 外文文献
获取专利

客服邮箱:kefu@zhangqiaokeyan.com

京公网安备:11010802029741号 ICP备案号:京ICP备15016152号-6 六维联合信息科技 (北京) 有限公司©版权所有
  • 客服微信

  • 服务号