#NILabVIEW编译器深层分析_第1页
#NILabVIEW编译器深层分析_第2页
#NILabVIEW编译器深层分析_第3页
#NILabVIEW编译器深层分析_第4页
#NILabVIEW编译器深层分析_第5页
已阅读5页,还剩17页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、NI LabVIEW 编译器:深层分析概览即便对无足轻重的编程语言来说,编译器的设计往往也是一个复杂的课题。即 使对专业的软件项目师们来说,编译理论也需要考虑专业知识。现代的 NI LabVIEW软件是一种多范例语言,包括广泛的多种类型的概念,包括数据流, 支持面向对象,以及事件驱动编程。 LabVIEW也覆盖了各种平台,服务多种操 作系统 (Windows, Linux, Mac, 多种芯片组 (PowerPC, Intel, 甚至可以服务于 嵌入式设备和现场可编程门阵列 (FPGAs,它与传统的 PC结构有明显不同。也许 您会猜想, LabVIEW 编译器是一个精密的系统,远远超出一般书面

2、描述的范围。目录编译与解释LabVIEW 编译器的历史回顾当今的编译器DFIR 与 LLVM 协同工作本专业论文介绍了 LabVIEW编译器,简明地讲解了它从 1986年 LabVIEW 1.0版 本开始的发展变化,并描述了它今天的形式。另外,本文也探究了最近编译器 的创新,并突出了这些新特点对 LabVIEW的益处以及对您的帮助。编译与解释LabVIEW是一种编译语言,它令人惊奇,因为它在一般的 G开发过程没有任何 明晰的编译步骤。取而代之的是,您可以对您的 VI 做出改动并简单地按下运行 键来执行它。编译意味着您所写的 G代码被转化为本地机器码然后被主机电脑 直接执行。这种方法可供选择的另

3、一种途径是解释,程序被另外的软件程序叫做解释程序)间接地执行,而不是直接由电脑执行。LabVIEW语言并没有要求其本身被编译或者解释;事实上, LabVIEW的第一个版 本使用解释程序。在后来的版本,编译器取代了解释程序以提高 VI 运行时的性 能,这是编译器与解释程序相比最常见的区别。解释程序更容易编写,并以较 差的运行性能为代价,而编译器执行起来更加复杂但是却能提供更快的执行时 间。 LabVIEW编译器最主要的好处之一就是编译器对所有 VIs 的提高显而易见, 而不必做出任何改变。实际上, LabVIEW 2018正式版的编译器进行了内部优化 以加快 VI 的执行时间。/ 16LabVI

4、EW 编译器的历史回顾在急于深入讨论现在编译器内部组成之前,很值得总结一下编译器从 20 年前最 早期版本到现在的发展。这里介绍的一些算法,例如类型传播法,聚丛法,以 及内嵌法 inplaceness ),在现代的 LabVIEW 编译器讨论中会更加详细地描述。LabVIEW 1.0 版本于 1986 上市。如前面提到的, LabVIEW在其第一个版本使用 了解释程序并且仅为摩托罗拉 68000 服务。那时的 LabVIEW语言非常简单,也 减弱了其对编译器的需求 当时是解释程序)。例如,它不存在任何的多态数据 类型,唯一的数据类型是扩展精度浮点数据。 LabVIEW 1.1版本首次引入了内

5、嵌 inplaceness )算法,或者称之为“内嵌程序”。此算法支持数据分配,因 此您可以在执行的时候重新使用数据,避免了不必要的数据副本,相应地,常 常能显著地提高执行性能。在 LabVIEW 2.0 版本,解释程序被当前的编译器所取代。仍然专门为摩托罗拉 68000服务, LabVIEW可以生成本地机器码。在 2.0 版本还增加了类传播算法, 可以在不断完善的 LabVIEW语言中发挥其它职能,处理语法检查与类型解读。 LabVIEW 2.0 另外一项重大的创新是聚丛程序的引入。聚丛算法支持 LabVIEW 图表的并行输入,并将节点归类为“丛”,它可以并行运行。类传播算法,嵌 入法 in

6、placeness )以及聚丛算法直到目前也是现代 LabVIEW编译器的重要组 成部分,并随时间的推移显现出更多的新增改进。 LabVIEW 2.5 中新的编译器 基本结构增加了对多种后端设备的支持,尤其是英特尔 x86 与 Sparc 。LabVIEW 2.5 也引入了连接器,当 VIs 需要被重新编译的时候,它可以管理 VIs 路径之 间的从属关系。在 LabVIEW 3.1,除常数合并外,也增加了两个新的后端, PowerPC 与 HP PA- RISC。LabVIEW 5.0 与 6.0 更新了编码生成程序,并增加了 GenAPI,一种与多 种后端连接的常用接口。 GenAPI交叉编

7、译对实时开发来说,是非常重要的。实 时开发者一般在主机 PC 上编写 VIs ,而将其部署到 将它们编译到)实时对象。 另外,一种循环不变代码移出的有限形式也包含在内。最终,LabVIEW多任务执行系统被扩展到支持多线程。LabVIEW 8.0 创建的基于 5.0 版本引入的 GenAPI基本结构,新增了寄存器分配 算法。在 GenAPI引入之前,每个节点的生成码都是由寄存器硬件编码的。不可 执行编码的有限形式以及死码删除也被引入。 LabVIEW 2009具有 64 位 LabVIEW与数据流中间表示 (DFIR。DFIR 立即被用于创建更先进形式的循环不 变代码移出,常数合并,死码删除以及

8、不可执行编码删除。 2009 新语言的特点, 例如并行循环,都是基于 DFIR 创建。最终,在 LabVIEW 2018,DFIR 提供了新的编译器优化,例如代数重组,公共子 表达式消除,循环展开,以及 subVI 直接插入。此正式版本也包括在 LabVIEW 编译器链中采用了低阶虚拟机 (LLVM。 LLVM是一种开放源代码的编译器基本结 构,广泛应用于工业生产。使用 LLVM,新增了很多优化,例如指令调度,循环 外提,指令组合,条件传播,以及一种更精密的寄存器分配程序。/ 16当今的编译器当对 LabVIEW编译器的历史有了基本了解后,您现在可以探索现代 LabVIEW的 编译器了。首先,

9、回顾高级的多种类型编译步骤概述,然后更加详细地浏览每 一部分。一个 VI 编译的第一步是类传播算法。这复杂的一步是为了解读适于终端输入的 隐含类型,并检测语法错误。在 G编程语言所有可能的语法错误都在类传播算 法这一步被检测。如果算法确定 VI 有效,编译继续。在类传播后, VI 首先被从结构图编辑器使用的模型转化为编译器使用的DFIR。一旦转化为 DFIR,编译器对 DFIR图执行几个变换,分解它 ,优化它,并使其 为生成代码做好准备。很多编译器的优化例如,内嵌程序 inplacer )与丛 聚程序被执行转化并在本步运行。在 DFIR 图标被优化与简化后,它被翻译成 LLVM中间表示。对 L

10、LVM一些列的扫 描被执行,通过中间表示来进一步优化并降低其阶次,最终变为机器码。类传播如先前提到的,类传播算法解读类型并检测程序错误。实际上,此算法包括如 下几个方面的功能:解读隐藏类型使其适于终端输入解读 subVI 调用并确定其合法性计算纵向检验 VI 的周期检测并报告语法错误此算法在您对 VI 进行每个改动后运行,以确定 VI 是否仍然完好,因此,这步 是否是“编译”的真正部分还存在少许争议。无论如何,它是LabVIEW编译链的一环,非常明显地相当于传统编译器的词法分析,句法分析,或者是语义分 析。一个适于终端输入的简单例子是 LabVIEW加法基元。如果您将两个整数相加, 结果是整数

11、,但是如果您将两个浮点数相加,结果是一个浮点数。类似的案例 出现在符合类型的数据,例如阵列和簇。存在其它语言结构,例如对移位寄存 器来说,有更复杂的输入规则。在加法基元的情况下,输出类型取决于输入类 型,类型被叫做通过图表“传播”,这也是算法名字的由来。这个加法基元的例子也表明类传播算法的语法检查职能。假设您连接一个整数 和一个字符串到一个加法基元会发生什么?在这种情况下,将二者的值相/ 16加没有意义,所以类传播算法将其报告为一个错误并将 VI 标记为“坏的”,它 会引起运行箭头中断。中间表示 是什么与为什么在类传播确定 VI 是有效的,编译器继续并将 VI 转化成 DFIR。一般来说在详细

12、 设计 DFIR之前要考虑中间表示 (IRs 。IR 是由编译过程通过多阶段编辑过的用户程序的表示。 IR 的概念常见于现代编 译文献并能应用于任何编程语言。图 1. AST IR实例请考虑一些例子。当今有多种流行的 IRs 。两种常见的例子是抽象语法树 AST) 与三地址码。t0 - yt1 - 3t2 - t0 * t1t3 - xt4 - t3 + t2表 1.三地址码 IR 实例图 1 显示了“x + y * 3 ”表达的 AST表示,而表 1 显示了三地址码表示 两种表示方式之间最明显的一处不同是 AST是更高级的。它更类似于程序 (C的 源表示而不是对象表示 机器码)。三地址码相比

13、之下,是低级的并且更类似于 汇编。不论高级或低级表示都有各自的优点。例如,语法分析,比如可靠性分析,对 类似于 AST 的高级表示比类似于三地址码的低级表示更容易实现。其它的优化, 例如寄存器分配或指令调度,一般用低级表示,比如三地址码来执行。因为不同的 IR 有不同的优势和劣势,所以很多编译器 ( 包括 LabVIEW会使用多 种 IR。在 LabVIEW中, DFIR作为高级 IR 使用,而 LLVM IR作为低级 IR 使用。/ 16DFIR在 LabVIEW中,作为高级表示的是 DFIR ,它是分等级且基于图形的,其本身类 似于 G代码。如同 G代码, DFIR也是由很多包含接线端的节

14、点组成。每个接线 端可以连接到其它接线端。一些节点,例如包含图表的循环,也可以相应地包 含其它节点。2. LabVIEW G代码与相应的 DFIR图表图 2 显示了一个简单的 VI 以及它的初步 DFIR 表示。当首次创建一个 VI 的 DFIR图表时,它是 G代码的直接翻译, DFIR 图表的节点一般与 G代码中的其它 节点进行一对一的通信。随着编译的进行, DFIR节点有可能被移动或者分开, 或者新的 DFIR 节点被加入。 DFIR一个最关键的优势是它保留了 G代码的固有 特性,如并行机制等。用三地址码表示的并行机制相比之下更难识别。DFIR为 LabVIEW编译器提供了两个显著的优势。

15、首先, DFIR 从 VI 编译器的表 示分离出编辑器。其次, DFIR能用作拥有多个前端和后端的编译器的公共端。 以下是每一个优势的详细解读。DFIR 图表从编译器表示分离出编辑器在 DFIR 出现之前, LabVIEW有一个单独的 VI 表示,由编辑器和编译器共享 这样阻止了编译器在编译过程中修改表示,这样一来,进行编译器优化就变得 困难了。/ 16图 3. DFIR 提供一种构架,允许编译过程中优化您的代码图 3 显示了对应于刚才提到的 VI 的 DFIR 图表。此图表描述了编译器过程较靠 后的部分,此时它已被几个变换分解并优化过。您可以看到,这个图表与之前 的图表看起来有很大的不同。例

16、如:分解变换已经移走了控制,指示,以及子VI 节点,而用新的节点替代它们 UIAccessor, UIUpdater, FunctionResolver 和 FunctionCall 。 循环不变式代码已从循环内将增量和乘法节点移出。聚丛法在 For 循环内部增加了 YieldIfNeeded 节点,可以使执行线程与其它竞争的工 作项目共享执行。我们将会在后面的章节对变换进行更深入探讨。DFIR IR 可以作为多个编译器前端与后端的公共端LabVIEW可以在数个不同的终端上工作,而其中一些终端与其它终端差别很大, 例如,一台 x86 台式 PC 与一个 Xilinx FPGA 。同样地, La

17、bVIEW为用户提供 了多种计算模型。除了使用 G语言的图形化编程, LabVIEW也在提供了例如 MathScript 的基于文本的数学运算。这就带来了前端与后端的集中,它们都需 要在 LabVIEW编译器下工作。使用 DFIR 作为公共 IR,前端进行生产而后端进 行消费,这样便促进了不同组合之间的重新使用。例如,运行于 DFIR 图表的常 数合并优化执行过程可以只需写入一次而用于台式,实时, FPGA以及嵌入式对 象。DFIR 分解一旦进入 DFIR, VI 首先运行一系列的分解变换。分解变换的目标是缩小或标准 化 DFIR 图表。例如,未连线输出通道分解会寻找在条件结构和事件结构中没有

18、 被连线并被配置为“ Use Default If Unwired ”的输出通道。对这些接线端来 说,变换赋给一个常量以默认值,并将其连接到接线端,因而使DFIR 图表的“Use Default If Unwired ”行为明确。随后的编译器扫描会完全相同地处理 这些接线端并假设它们都有连线的输入。在这种情况下,语言的“Use DefaultIf Unwired ”特征在将表示缩小到更基本的形式后便被“编译掉”了。这种做法也可以用于更为复杂的语言特性。例如,分解变换被用于将反馈节点 缩小到 While 循环上的移位寄存器中。另外一个分解将并行的 For 循环分解为/ 16几个顺序的具有额外逻辑

19、的 For 循环,用以为顺序循环将输入分解为可平行化 的部分,随后将所有的部分再次组合到一起。LabVIEW 2018的一个新特征,子 VI 直接插入,也是作为 DFIR分解来执行。在 编译的这个阶段,被标记为“直接插入”的子 VI 的 DFIR图表直接加入调用程 序的 DFIR 图表。除了避免子 VI 调用的架空,直接插入法通过将调用与被调程 序结合到一个单独的 DFIR 图表,为额外的优化提供了可能性。例如,考虑一个 从 vi.lib. 调用 TrimWhitespace.vi 的简单 VI 。图 4. 用于演示 DFIR 优化的简单 VI 实例TrimWhitespace.vi 在 vi

20、.lib 中定义如下:图5. TrimWhitespace.vi 结构图子 VI 直接插入调用程序中,得出等价于如下 G代码的 DFIR 图表。/ 166. 直接插入的 TrimWhitespace.vi DFIR 图表的等价 G 代码既然子 VI 图表直接插入调用程序的图表,不可获取代码的删除和死码删除能够 简化代码。第一个条件结构总是执行,而第二个条件结构从不执行。7.因为输入逻辑是常量,条件结构可以删除类似地,循环不变代码将匹配类型的基元移出循环。最终的DFIR 图表等价于如下的 G代码。图 8. 最终 DFIR 图表的等价 G 代码因为 TrimWhitespace.vi 在LabVI

21、EW 2018 版本中默认标定为直接插入,所有 此 VI 的客户端使用都能自动享有这些益处。/ 16DFIR 优化在 DFIR 图表完全地分解后, DFIR优化扫描开始。更多的优化在随后的 LLVM编 译时被执行。本章节仅讲述了众多优化当中的一部分。这些变换都是常用的编 译器优化,所以,想找到更多具体优化的信息应该较为容易。无法读取代码的删除无法读取代码是永远无法执行的代码。删除无法读取代码并不直接让执行变得 更快,但是它可以使您的代码更精简并且改善编译时间,因为删除的代码在随 后的编译扫描中将不再被访问到。在无法读取代码删除之前After Unreachable Code Eliminati

22、on图 9. DFIR 无法读取代码删除分解的等价 G 代码/ 16在这个例子中,条件结构的“ Do not increment ”图表从不执行,所以变换删 除了这个条件。因为条件结构只剩下一个条件分支,因此它被顺序结构替换。 随后的死码删除移除了边框与枚举常量。循环不变代码移动循环不变代码移动将识别循环内部可以安全移至外部的代码。因为移出代码的执行次数更少,整体执行速度将得到改善。循环不变代码移动变换之前循环不变代码移动变换之后图 10. DFIR 循环不变代码移出分解的等价 G代码在这个例子中,增量运算被移到循环外面。循环本体不变,因此在创建数组的 同时,不必在每个迭代重复进行计算。公共子

23、表达式删除公共子表达式删除可识别重复计算,而将执行一次计算,并重复使用计算结果/ 16BeforeAfter图 11. DFIR 公共子表达式删除分解的等价 G 代码常数合并常数合并支持那些在运行时是常数的图表部分,因而可以在初期就确定下来图 12. 常数合并在 LabVIEW 结构图中非常直观图 12 中 VI 的哈希码指出了常数合并的一部分。在这种情况下,“偏量”控制 不能够常数合并,而加法基元的其它操作数,包括 For 循环,是恒定值。循环展开循环展开通过在合成码部分多次重复一个循环的本体以及减少相同因子总的迭 代计数,减少了循环架空。这样减少了循环架空,并且在代码尺寸增长损失的 情况下

24、,显露了更多的优化过程。死码删除死码是多余的代码。去除死码加快了执行时间,因为去除的死码不再被执行。 死码并不是由您直接编写的,它常常由 DFIR 图表转换操作产生。请考虑如下的例子。无法读取代码的删除确定事件结构可以被移除。这样“创建”的死码可 以被死码删除转换移走。/ 16先前在无法读取代码删除后在死码删除后图 13. 死码删除能够减少编译器需要跨越的代码数量此小节涉及的大部分转换具有像这样的相互关系;运行一个转换也许会引发其 它转换运行的机会/ 16DFIR后端转换在 DFIR 图表被分解并优化后,很多后端转换被执行。这些转换评估并注解DFIR,为最终将 DFIR图表降低为 LLVM I

25、R做好准备聚丛程序聚丛算法分析 DFIR 图表的并行机制,并将节点归类为您可以并行运行的丛。这 种算法与 LabVIEW实时执行系统紧密联系,这些系统使用多线程协同多任务处 理。每个由聚丛程序产生的丛都作为执行系统的单独任务罗列出来。丛中的节 点以固定的,串行化的次序执行。每个丛具有预订的执行次序允许替代程序共 享数据分配并显著地提高了性能。聚丛程序也具有将结果插入长操作的职能。 例如循环或者 I/O ,因此这些聚丛程序与其它聚丛程序协同执行多任务处理。内嵌程序内嵌程序分析 DFIR 图并识别什么时候您可以重新使用数据分配以及什么时候您 必须进行复制。 LabVIEW中的一个连接也许是一个简单

26、的 32位标量或 32 MB的 阵列。确保数据尽可能地重复使用对 LabVIEW这样的数据流语言来说是至关重 要的。请考虑如下的例子 请注意 VI 调试不能实现最好的性能和存储器空间占用)图 14. 简单实例演示了内嵌算法这个 VI 初始化一个阵列,对每个要素增加了一些标量值,并将其编写为一个二 进制文件。应该有多少个阵列副本 ? LabVIEW最初不得不在本地创建阵列,而 加法运算只能在那个阵列运行。因此只需要一个阵列的副本而不是每个连接都 分配。这意味着一个显著的不同无论是存储器消耗还是执行时间如果 阵列很大。在这个 VI ,内嵌程序意识到运行“内嵌”的时机并配置加法节点以 利用它。您可以

27、在 Tools?Profile 下使用“缓冲区分配”来检验您编写的 VIs 的这种行 为。工具不会显示加法基元的分配,而显示为没有数据副本并且加法运算内嵌。/ 16这是可以接受的,因为没有其它节点需要原始阵列。如果您如图 15 所示修改了 VI ,内嵌程序会为加法基元制作一个副本。这是因为第二次 写为二进制 Write to Binary File )需要原始的阵列并且必须在第一次 写为二进制基元 ,以描述节点 的功能性。 IL 提供了一个独立平台来描述节点的低级行为。 IL 的多种指令被用 来执行运算,读写存储器,实现比较与条件跳转,等等。 IL 指令能够对存储器 或用来存储中间值的虚拟寄存器中的值进行操作。常用 IL 指令包括 GenAdd, GenMul, GenIf, GenLabel, 及 GenMove。在 LabVIEW 2009 及早期版本中, IL 结构直接转化为用于对象平台的机器指令( 例如 80X86 与 PowerPC。 LabVIEW使用一个简单的一次扫描寄存器分配程序 将虚拟寄存器映射到物理机器寄存器。每个 IL 指令发出一组用于特定机器指令 的硬件编码,从而在每个支持对象的平台执行它。它盲目地追求速度,是一种 即席是一种多用途,高性能,开放源代码的编译器构架,起初作 为伊利诺斯州立大学的一个研究项目被发

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论