《我和 LabVIEW》更新总结

大约去年这个时候,我把《我和 LabVIEW》这本书做成电子书发布在了网站上。当时已经计划每年会做一些更新,今天回顾了一下这一年来的改动,实际上改进的内容比我预估的还更多一些。

刚开始做准备的时候,我担心我的 LabVIEW 编程技术已经忘光了,也担心这十年来 LabVIEW 变化太大,打开之后都不认识了。但后来发现,我开始的预计都有些太悲观了。我的编程技术确实退化了,但基本的方法都还记得,恢复的很快;LabVIEW 也并没有太大变化,基本操作几乎一模一样。

变化比较大的是网上的中文资料。10 年前网上关于 LabVIEW 的中文资料少得可怜。但现在,无论哪一方面都能搜到一大堆资料,而且很多资料的质量也是相当高的。这些年也有不少关于 LabVIEW 的中文书籍出版,网上评价也不错。如果我在国内的话,肯定当时就买来看看了,不过现在人在国外考虑到运费,嗯,还是等下回回国再说吧。

再有,就是近几年视频逐步崛起,成了网上最重要的知识分享方式。但是考虑到我个人还是会继续以文字加图片的方式分享自己的编程经验。这主要是因为视频制作要花费更多的精力,如果我有时间,还是倾向于增添更多的内容。另外,就是我分享的很多东西也不是太成熟的,发布后还会经常改来改去。文字改起来非常容易,视频要改其中部分内容就麻烦多了。

在搜索 LabVIEW 中文内容的过程中,我还特意尝试用了很多国内的搜索引擎,结果大失所望。百度是国内市场占有率最大的搜索引擎了,先不说它垃圾广告的问题,单说搜索质量也是十分差劲。搜索一些技术内容,返回的常常都是灌水文,最后只能换回 Google。

相比起写纸质书,我还是更喜欢这种类似博客的电子书发布方式的。最主要的原因是可以及时和读者交流。从读者的反馈和问题可以直接了解到自己那里写的不清楚,甚至写错了,然后可以及时纠正。除了读者留言,我还使用了网站统计工具来查看这本书被阅读的情况,工具可能并不精确,但大致可以反应读者的分布。

首先让我觉得比较吃惊的是,这本书最大的流量来源是 Google,尽管在国内无法直接访问 Google。这一说明了搜索引擎对一个网站的流量作用极大,可惜的是,国内的一众搜索引擎都不能搜索我的网站,可能是歧视我没钱买关键词吧。唯一一个我发现能搜索我的网站的国产搜索引擎是 https://fsoufsou.com/ 。也试了一下用这个网站搜索技术文章,比百度的结果好太多了。可惜它的用户量太少,几乎可以忽略不计。希望这个搜索引擎能继续坚持做下去。

除了 Google,比较多的来源网站是知乎和微信。知乎上网友的推荐贴给本书带来了不少流量。微信的对话和群都是封闭的,而我又不在任何群里,就无法得知网友们都在讨论啥了。

再有就是发现绝大多数读者关注的还是基础内容,而不是 LabVIEW 一些高级用法。访问量前五的章节是《数值和布尔数据》,《Hello World 程序》,《图形化显示数据》,《什么是 LabVIEW》,《数组和循环》。深入一些的内容比如性能优化,面向对象等访问量比起基础内容零头都不到。我想想也是有道理的,那些比较高级的用法,可能并不是看看书就会用的,最好还是要有交流,有个专门的人来指导一下。相反写书的话更适合介绍一些基础知识。所以我也调整了自己的更新维护计划。今年上半年的时候,我重写完善了面向对象一章。后来,就把主要时间用来从头更新那些基础章节了。

广告

训练一个黑白棋模型

之前用 LabVIEW 编写了一个黑白棋程序,作为学习 XControl 的示例。那个程序基本完整,但是缺少一个 AI 自动走子的功能。最近抽空尝试训练了一个网络模型,添加到了演示程序中。

所有AI下棋的思路都是非常类似的:根据当前的盘面,给每个可以落子的位置打分,选取分数最高的那个走法。这里的关键就是设计一个最为合理的打分算法。

黑白棋最终判定胜负的标准是看谁的子多,所以最符合直觉的打分方法是:在一个位置落子后,把己方的棋子数目作为这个位置的分数。这个算法有个缺陷,就是当前棋子数最多的走法不见得就能保证将来也棋子数最多。

解决这个缺陷的思路有两条:其一是,不要为当前这一步打分,而是向后预测几步。比如,把双方各下三步棋(也就是总够六步)后的所有可能出现的局面都列出来,然后看那个局面得分最高。考虑的步数越深,棋力也就越高,极端情况,如果预测的足够深,甚至可以找到必胜的下法。这个方法的缺点是预算量非常高,增加预测的深度,计算量会指数级增加。可以通过剪枝做一些优化,但效果有限。

第二条思路是采用更复杂的打分算法,如果只考虑棋子数量还不够好,那么就也同时考虑落子的位置,稳定的棋子的个数,周围空间的个数等等。在这众多的因素中哪些更重要,如何分配权重,对于我这种非专业棋手来说是很难做选择的。不过这部分可以利用机器学习算法,让电脑自己找到最优解。

当然以上两条思路可以结合使用:先找到一个最优的打分算法,再尽量预测到更深的步数。

我尝试训练了一个基于神经网路的打分模型。

我采用的是一个只有一层隐藏层,64节点的全链接神经网络。单隐藏层64节点的神经网络对于解决黑白棋来说,有点太小了。我也不能确定多大才合适,但估计至少也应该采用一个七八层的CNN,才能达到不错的效果。不过,我的目标不是最好的效果,而是只要试验一下可行性。并且,我需要把训练好的模型移植到 LabVIEW 代码上,复杂模型移植起来太麻烦。所以我选择了这个最简单的模型。

模型的输入数据是当前棋盘状态(每个位置棋子的颜色)和一个准备落子的位置,模型输出一个实数表示得分。

训练模型的大致思路是:让模型分别持黑子和白子,自己跟自己下棋,并且记录下每一步的走法。最终胜利一方所有走过的位置都获得正标签,失败一方所有走过的位置都是负标签。用这些标签训练模型,然后重复以上过程。

在训练模型的过程中,我遇到了一些以前从未考虑过的问题。比如,采用何种激活函数。我开始采用的 ReLU 函数,但模型始终无法被训练到预期效果。调试后发现,是 ReLU 的神经元坏死造成的。ReLU 在输入值小于零时,梯度永远为0,这个神经元很可能再也不会被任何数据激活了。这对于目前常见的有着几万乃至几百亿节点的大规模模型来说,根本不是问题。但是对于一个本来就没几个节点的小型模型来说,损失神经元是非常致命的。我把激活函数换成 Sigmoid 之后,效果就好了很多。

我训练模型的方法效率非常低下。在下棋过程中,大多数的步骤可能都不是太重要,不论走这还是走那,对最终结果影响都不大。关键的就那么少数几步,可以决定输赢。但是在训练时候,我也不知道每一步的权重应该有多大,只能假设所有步数都是一样的权重。那些垃圾步不但占用资源,也会影响训练结果。

模型训练好之后,我把它移植到了之前编写好的 LabVIEW 黑白棋 VI 中。棋力算不上很好,但至少是明显优于随机落子。等我空闲的时候,看看还能不能进一步优化下。

程序在: https://github.com/ruanqizhen/labview_book/tree/main/code/%E7%95%8C%E9%9D%A2%E8%AE%BE%E8%AE%A1/Xcontrol/Othello

LabVIEW 的 unicode 问题

以前从来没有意识到过这个问题,直到最近,我试图去查看自己很久之前写的一些VI的时候才开始注意到它的。

先简略的介绍一下unicode的背景:

计算机是在美国被发明的,所以很自然的最开始只考虑了处理英文。早期最著名的关于如何在计算机中表达字符的标准是ASCII标准(American Standard Code for Information Interchange,美国信息互换标准代码)。它定义了128个字符,包括英文字母大小写,数字,常用的标点和一些特殊符号。当时世界上所有的计算机都用同样的 ASCII 方案来保存英文文字。这个方案最大的问题是只包含了英文字母,于是其它国家,组织和公司纷纷开始扩展这个标准用以支持其它字符,比如制表符、数学运算符号、中文字、日文字等等。比如在中国最常用的标准 GB2312,GBK,GB18030 等都是对 ASCII 的中文扩展。之后扩展出的这些标准有个很大的问题,就是同一个数值在不同的标准中被定义了不同的含义。某一数值在中文的标准下可能是一个中文字符,放到韩国的系统的某标准下,可能就是一个完全不相关的制表符。这就造成了,在中文环境下开发的软件,运行到韩国系统下显示的就完全是乱码。如果有人需要在一个系统下同时运行一个中国软件和一个韩国软件,恐怕就不行了。

为了解决这个问题,1990年开始,计算机业界开始研发一种编码标准,它可以覆盖全世界所有的字符,这样任何一个字符都有自己独占的编码,这样就不会出现换个系统就乱码的问题了。这就是unicode,也叫万国码、单一码。unicode规定了字符集,但是这套字符集也还有多重不同的编码格式。Windows采用了UTF-16LE格式的unicode编码(UTF全称为 Unicode Transformation Format),使用16位的(双字节)数据表示一个字符。但是当前最流行的unicode编码格式却是 UTF-8,这是一种变长的编码方式,根据字符的常用程度,可能由1到6个字节来表示。目前大多数的unicode文档采用的都是 UTF-8 编码的。

目前,大多数的软件也都是基于unicode的了。但是LabVIEW始终没有支持unicode。虽然Windows很早就开始支持unicode了,但是为了兼容那些还没有支持unicode的软件,Windows系统保留了一个默认字符集的设置,对于非unicode的软件,Windows会使用默认的字符集来解释那些字符编码。国内使用的电脑几乎都设置了默认的字符集为中文字符集,所以,一个软件是不是支持unicode,对于绝大多数中国用户来说根本感觉不到什么差别。所以,我之前也从来没觉得LabVIEW不支持unicode有什么问题。

直到最近我才发现了问题。我家里有两台电脑,一台操作系统安装了英文的Windows,并且没有修改过默认的非unicode字符集;另一台操作系统是中文Deepin Linux。我在两台电脑上都安装了社区版的LabVIEW 2021。我想在两台电脑上查看一下自己十年前写的一些VI,这时才发现,如果VI的路径中包含中文,是无法在Windows上被LabVIEW打开的。不过,我主要使用的是Linux,在Linux上还可以打开大部分的VI。但很快我就又发现,如果那些VI中如果有我以前设置的中文的常量或注释,在Linux下是无法正确显示的。但是呢,我还可以再Linux下添加中文常量或注释。之后,又遇到了更严重的问题:以前那些项目中,如果有中文名的子VI,或是库中、类中有中文名的VI,就通通都打不开了。我开始还以为是不是10年过去了,LabVIEW系统有什么变化,但是没听说过啊。于是我开始怀疑是不是文字编码的问题,我把Windows的默认字符集换乘了中文,果然在Windows下一切正常了。而且我还发下,在Linux下,给VI添加的中文注释,拿到Windows下,看到的全是乱码。

总结我看到的现象,我深刻怀疑,目前LabVIEW在Windows下是不是用unicode的,字符编码由Windows系统决定,对于大部分中国用户来说,采用的是GB18030中文编码;但在Linux下却使用了UTF-8编码。

在Linux由于系统和LabVIEW采用的都是unicode,所以一个VI在不同Linux版本下应该行为都是一致的。但Windows系统用的是unicode,LabVIEW用的却不是。我们现在假设一个项目中有两个VI,本别是“界面.vi”和“任务1.vi”,其中“任务1.vi”是“界面.vi”的子VI。在操作系统层面,有两个文件:“界面.vi”和“任务1.vi”,它们的名字都是使用unicode保存的。但是在“任务1.vi”内部,它记录了自己要调用“任务1.vi”,却用的是非unicode编码保存的子VI的名字。所以LabVIEW中用于记录这个子VI的一段二进制数据与操作系统中记录这个子VI文件名使用的二进制数据是不同的。每次LabVIEW需要操作系统找到相应的VI文件时,还需要做一次编码转换,把文字转为系统认识的unicode编码。如果保存VI,和读取VI都是在中文Windows中,这没有问题,VI名字总能被正确转换。但是把之前在中文Windows系统下保存的项目拿到非中文Windows(默认语言编码不是中文)下打开,文件名编码转换这一步就会出错,LabVIEW就无法找到正确的VI了。同理,中文Windows下保存的VI,如果有中文名,拿到Linux下打开会出错;反过来也是一样会出错。

总之,由于LabVIEW在Windows下没有使用unicode,造成了中文显示在不同系统下的不一致行为,切换系统就会出现乱码,甚至VI无法被加载。目前没有什么好办法可以解决这个问题。我只好将来在LabVIEW中只使用英文,不是用中文了。

重新制作了书籍网页

最近学习完了React,又使用Docusaurus重新为书籍制作了网页:https://lv.qizhen.xyz/

实际上,Docusaurus已经帮我作为绝大部分工作,我学习的React技能基本没有用上,毕竟书籍的格式还是比较简单固定的,不需要非常炫酷的界面。

之前是用Docsify制作的页面。我对于Docsify的界面和功能已经非常满意了,最主要的问题是它制作的网页是客户端渲染的,搜索引擎都不会爬取。墙外的搜索市场已经被Google垄断,恐怕Google是不会有动力去支持搜索客户端渲染页面了。国内的搜索市场竞争还更激烈一点,可以它们居然做的都比Google还差很多,也没有指望。只能自己修改网页了。

Docusaurus使用起来比Docsify麻烦不少,所需技术门槛也稍高。这一是因为Docsify只支持制作文档网页,而Docusaurus还可以制作个人主页和博客,功能复杂了不少,虽然都是我都用不上的。二是因为Docusaurus是制作服务端渲染页面的,多了一个Build步骤。再有Docusaurus比较新,资源相对来说少一些。

但不管怎么样,Docusaurus也是非常令人满意的。现在Google可以爬取我的页面了,国内的搜索引擎都还不行,但这不是网页的问题,最可能是我的域名没有工信部认证,国内引擎不愿意访问。

Docusaurus开发团队非常活跃,应用前景应当超过其它类似工具。非常推荐用于创建静态的个人主页、博客、文档等。

最近更新了书里的一些内容

https://lv.qizhen.xyz/

毕竟不是专业做LabVIEW编程了,写作速度也大为减缓。因为之前太久没维护了,近期我还是会尽可能多的做一些修改。长期的来说,希望保持每年可以更新一两章的内容。

将来会逐渐补充一些我比较了解的内容,比如:面向对象编程,泛型编程,数据结构,设计模式,网络编程,调用Python等。当然这些都是很长远计划了,不能期望太高。

试了一下LabVIEW2021社区版

只大致看了看前后面板。发现跟10年前差别不大,功能有所增加,但也没有太多,没有2000~2010那十年变化大。这也可以理解,编程语言的基础部分还是要稳定一些的。重大的功能添加可能主要会以插件的形式发布了。

也有可能NI过去10年精力主要都放在了LabVIEW NXG上面。看看下个十年会怎么样吧。

放在GitHub上的书还没法被检索

我把书的原稿开源放到GeiHub上之后,给它做了个页面方便阅读。做好的网页是一个单页面应用,也就是说书里的所有内容都是放在同一个页面上:https://lv.qizhen.xyz/。别看每个章节都会在地址栏上显示不同的URL,比如“https://lv.qizhen.xyz/#docs/structure_condition”,其实#号后面的都只是同一页面不同的参数而已。浏览器只能展示 HTML 格式的内容,但GitHub的网站上并没有存HTML格式的文档,所有文档使用Markdown(*.md)格式保存的。GitHub的网站也不会把MD格式文件动态渲染成HTML格式再传给读者的浏览器,网站发给浏览器的还是 MD 格式的数据,是浏览器端运行了一段JavaScript程序,才把 MD 格式的数据渲染成了HTML。

GitHub的服务器应该是不支持后台渲染的,做成前台渲染的单页面应用,用户体验会好一些:换页反应比较快,还可以随时改变背景配色等。但是这种做法对搜索引擎非常不友好。

搜索引擎在检索网站时,一是它不会尝试不同的参数,也就是说它只检索 https://lv.qizhen.xyz/ 这一个首页面;二是搜索引擎的爬虫也不会运行JavaScript,所以即便是首页的内容,它也看不到。总之搜索引擎根本看不到这本书的任何内容,也就别想搜索到它了。

我还不知道这个问题怎么解决,不过GitHub上搭建博客还有很多其它方法,有一些是产生静态HTML的,应该没有这个问题。等有时间再试试其它的那些工具吧。

GitHub自带的搜索也几乎没法搜索中文书,它是按词搜索的。它定义的“词“就是两个空格或者符号之间的字符串,这对于代码或者英文文档都非常合适。但是中文的词和词之间是没有空格的,GitHub对于中文只能整句匹配,只有文章里恰好有跟搜索内容一模一样的句子时才会被找到。

终于把所有章节都放好了

https://lv.qizhen.xyz/

花了好几天时间,有一天我还请了休假专门来弄它。还依然存在的一些排版问题就只能慢慢修改了。

Markdown 格式在某些方面的功能还是不太够,最主要的问题可能是表格,无表头的表格都不支持。Word文档转换到Markdown,大部分的表格都是直接使用了HTML标签。显示到时没有问题,但如果要编辑就困难了,满眼看去,全是标签,都找不到需要的内容。

还要特别感谢 lhb5883 网友,最初建议我在Github上搭建博客。我这才惊喜的发现原来Github没有被墙(但愿我不是乌鸦嘴),而且Github也确实是最适合放置开源项目的网站。唯一担心的是LabVIEW用户可能很多都不熟悉Github。

这堵墙给我造成了很多麻烦,更糟糕的是,它还越来愈高了。墙里的可能以为它只是为了圈住里面的人,但实际上它是双向的。我访问国内网站也是越来越困难了。比如我已经完全无法使用新浪博客了,最开始我还可以浏览页面,但是不能看评论,现在正文也经常加载不了了。而且如果要登陆,还要每次让我用国内的手机收验证码。目前,国内用户量比较大的网站都必须要使用手机注册和验证了,也就意味着大多数的常用网站我都无法使用了。如果只是注册需要手机,必要时我还可以请国内亲戚帮个忙,但有些网站每次登陆都要手机验证,就真是彻底不让国外用户使用了。

这次把书开源的过程中还是学习了一些建网页的知识的,之前基本没有任何编写网页的经验。不过对于把博客迁移到Github上,我还是有些犹豫。主要是之前博客的内容很多,还包含大量的图片,链接,留言等等。这些要迁移起来工作量恐怕比迁移一本书要大得多,不知要花多少时间。所以我还得再研究研究,有没有什么更简单的做法。

打算把《我和LabVIEW》这本书的原稿开源了

我想出版社不会需要再改版或印刷这本书了,于是今天联系了北航出版社,询问他们是否同意我把书的原稿开源出来,这样也许还能帮助更多一些人。北航出版社的胡主任给了我肯定的答复,非常感谢他们的支持!

我会在Github上建一个项目,然后把原稿和相关的示例都放在里面。我也没有这本书最终的电子版,原稿与发行版比,内容是相同的,主要是少了漂亮的排版。

我正在研究如何使用GitHub Page的功能。原稿是Word文档格式,但我想把它转换成Markdown格式再上传GitHub,这样可以方便阅读,检索和讨论。应该用不了多长时间就可以把项目建好。要是将来还有人愿意参与进来,那就好了。

可重入VI的两种数据空间分配方式

https://labview.qizhen.xyz/

新版本的LabVIEW在设置VI为可重入时,还有两个选项:“为每个实例预分配空间”和“让各个实例间共享空间”:

image

“为每个实例预分配空间”是旧版本LabVIEW(8.6之前)设置可重入VI时的唯一选项。它是指程序在运行前,编译的时候就为每个可重入VI的实例分配好各自的数据空间。比如说这个子VI被主VI在3处不同的地方调用了,那么就分配3份数据副本给它。但是,这种分配数据空间的方式有两个主要的问题。

其一,很多时候程序运行前不能预期会有几处调用到了这一个可重入子VI,比如递归算法就是如此。递归每迭代一次,就需要生成一个新的递归VI的实例。而递归的深度与输入数据有关,在程序运行时才能确定,因此无法预先得知应当分配多少个副本数据空间,也就没法设置为这种模式。

其二,这种设置方式控件效率太低。假设一个可重入VI A,它在主VI中有三处被调用,分别是实例1、2、3。假设程序总共运行3秒,第0秒运行实例1;第1秒运行实例2;第2秒运行实例3。在这个程序整个运行时间内,始终为可重入VI A开辟了三份数据空间,但是每份数据空间都只被使用了很短一段时间,其余时间都没被用到。若是能够重复利用它们,程序可以节约不少内存。

为了解决以上两点问题,LabVIEW又新添了一种为可重入VI 分配数据空间的方式:“让各个实例间共享空间”。个人觉得这个名称起的很不好,它容易让人误解为:只为可重入VI分配一份数据空间,然后它所有的实例都是用着份数据空间。实际情况当然不是这样的,否则就无法保证可重入VI功能的正确性了。

事实上,采用“让各个实例间共享空间”时,每个可重入VI 的实例让然会有它自己独立的数据空间。程序在逻辑功能上与“为每个实例预分配空间”的方式没有任何区别。唯一的不同在于,数据空间并不是程序一起动就分配好了的,而是只有当一个实例VI被运行到的时候,才为它分配数据空间,而这个实例VI运行结束后,它的数据空间就又被回收回去了。两个同时运行的实例VI是绝对不会共享同一个数据空间的;两个不同时运行的实例VI倒是可以分享同一片数据空间,这已是名称中“共享”的由来。

还是以上文的可重入VI A为例,若它被设置为“让各个实例间共享空间”,程序开始时,只需要为可重入VI A准备一份数据空间,供实例1使用;程序第1秒的时候,实例1运行结束,它的数据空间被收回,因此实例2还可以继续使用这份数据空间;第2秒时,轮到实例3继续使用者份数据空间。若我们改变一下应用程序的逻辑,把实例3在第2.5秒的时候就运行起来,由于原有的一份数据空间还在被实例2所使用,它不能再分配给实例3,这时候,LabVIEW就会为可重入VI A再开辟一份数据空间,供实例3使用。

“让各个实例间共享空间”的设置虽然提高的程序的空间效率,但它并非只有有点,因为数据空间的开辟回收都是需要时间的。因此它实际上是以牺牲时间效率为代价来换取空间效率的提高。

对于一个可重入VI,如果它在应用程序中被调用的地方并不是很多,或者它的各个实例常常会同时运行的,那么就应该把它设为“为每个实例预分配空间”;反之,这个可重入VI会被应用程序频繁的调用,而且每个实例运行的时间都很短,它们的运行时间不大可能会有重叠,就应当把它设置为“让各个实例间共享空间”。用于递归调用时,必须使用“让各个实例间共享空间”方式。

《我和LabVIEW》补充内容和注释 第2版

这是在本书第二版完稿后,我记录的一些使用LabVIEW的经验。这些文章可以作为对书中内容的补充。

针对书中章节 简要说明 详文链接
4.2 可重入VI 可重入VI的两种分配数据空间方式 可重入VI的两种数据空间分配方式
11 代码风格与优化 像其他语言程序那样,把可执行代码与源代码分离开来 可执行代码与源代码分离
11 代码风格与优化 内嵌子VI对程序效率的影响 内嵌子VI

返回《我和LabVIEW》主页

内嵌子VI

https://labview.qizhen.xyz/zheng-wen-mu-lu/optimization/optimization_inline_subvi

从提高程序的可读性、可维护性、可重用性的角度来说,在设计LabVIEW程序时,应当经可能多的使用子VI。基本上,每个相对比较独立的功能都应当被做成子VI,而子VI最大也不应到超过30个节点。

但子VI过多,可能会对程序的运行效率带来一定影响。

首先,调用子VI是有一定的开销的,比如调用子VI时需要把参数压栈等,但是这些开销是非常小的,可以忽略不计。

造成嵌入式子VI提高整个程序的性能的主要原因是在于LabVIEW编译器的优化工作。LabVIEW编译器是可以比较智能的做一些优化工作的,在不改变程序逻辑的前提下,提高生成代码的执行效率。比如下面列出了其中几种常见的编译器优化方法:

  • 去除死代码:把永远的不会被执行到的代码删除。
  • 转移循环中的不变量:若循环每次迭代都做某些相同的运算,编译器会把这个运算挪到循环之外,制作一次就可以了。
  • 相同代码合并:编译器自动发现程序中对同一数据进行的重复运算,把重复的运算去掉。
  • 常量合并:编译器会发现程序中对常量进行的运算,在编译时就计算他们的结果,把结果直接保存在程序中,这样就不需要每次程序运行都对其进行计算了。

LabVIEW编译器的优化有一个局限性,就是这些优化措施只能在一个VI上进行,不能应用于全局。当把一个子VI B的代码合并到上层VI A中去,编译器可能就会发合并后的代码有很多可以优化的地方;若VI A和B的代码分别在不同的VI中,编译器分开查看每个VI中的代码,可能就找不出太多可以优化的地方。

LabVIEW中有一个解决方案,可以兼顾可读性与运行效率,就是在编辑程序时,多划分子VI,而编译程序时,又把子VI合并起来当作一个大VI来处理,这就是嵌入式子VI。

在VI属性对话框的“执行”页面上有个选项是“在调用VI中内嵌子VI”,英文叫“Inline SubVI into calling VIs”。若读者学过C语言和它的inline函数,就会非常容易理解LabVIEW中的这个设置。

image

当这个选项被选中,这个VI就变成了嵌入式子VI。当嵌入式子VI被拖拽到其它VI上,从编辑代码的角度上看,它与其它VI没有什么区别;但是在程序编译的角度来看,它与普通VI是不同的,那就是嵌入式子VI在编译时,并不是独立存在的,它的代码被全部复制到了调用它的VI中。用一个实际的例子来讲,假如一个程序中有两个VI,A和B,A调用了B。假如B是一个普通的VI,这个程序便编辑成可执行代码后,代码中还是有两个VI,A和B;若B是嵌入式子VI,编译好的程序就只剩下一个VI了,被扩充了的A,被扩充的A中包含原A和B两个VI的代码。

需要注意的是:嵌入子VI这个选项并不是用的越多程序效率就越高。使用嵌入式子VI也会给效率带来负面影响,比如它在每处调用子VI的地方都会生成一份拷贝,使得程序体积膨胀,占用过多的内存等。因此,建议把调用不频繁,输入参数常常为常量的VI设为嵌入式子VI,而一般的子VI不需设置。

下面用一个具体的示例来看一下LabVIEW编译器是如何优化程序的:

首先,我编写了一个子VI,这个VI有三个输入;其中两个输入是数据,另一个输入表示对两个输入数据进行何种运算,是相加还是相减等;让后把运算结果输出。

image

image

这个“Inline sub VI.vi”被设置为嵌入式子VI。对于嵌入式子VI,它必须是可重入的(嵌入式子VI的代码在每个子VI被调用的地方都会有一个副本,就更不要说是数据空间了)。LabVIEW暂时不支持嵌入式子VI的调试和自动错误处理。所以,在VI属性对话框中设置嵌入式子VI时,要把其它的设置做相应改动,否则LabVIEW会在其它设置项上打个叹号,提示这里的设置有问题。

image

接下来我在下面的程序中调用的这个子VI:

image

下面咱们要看一下,LabVIEW的编译器是如何对这个程序进行优化的。为了更直观的展示这些优化,我会画一些示意程序框图。这些用于示意的程序框图并不是LabVIEW优化出来的,而是我手工制作的,LabVIEW的优化只针对编译好的可执行代码,它并不会修改VI的源代码(程序框图)。但是经过LabVIEW的优化,main.vi生成的可执行代码,与我制作的示意程序框图编译成的可执行代码是完全相同的。

因为“Inline sub VI.vi”是嵌入式子VI,对于编译器来说,它的代码是被拷贝到main.vi上来的,所以对于编译器来说,它看到的代码是这样的:

image

在这段代码中,条件结构分支选择器的输入是一个常量“Add”,这就意味着程序每次都只会进入“Add”这一分支,而其它分支永远不会被执行到。编译器会把那些执行不到的分支移除,因此,优化后的程序代码等效如下:

image

程序中循环内所作的运算,在每次迭代中都是相同的,因此它可以被挪到循环之外,只运行一次。优化后代码等效如下:

image

程序中的平方运算的输入值是一个常量,因此这一运算会在编译时就完成这一计算,等效优化后代码如下:

image

程序中,对data这个输入数据进行两次完全相同的运算,这是没有必要的,编译器也会将其合并,于是优化后的代码最终等效与如下的代码:

image

可见,一个看似复杂的程序,经过LabVIEW编译器的优化,它的运行效率可以媲美一段极其简单的程序。但是这并不是说程序员可以不关心代码的效率了。编译器毕竟还是能力有限,它只能做简单的优化,程序效率的决定因素还是在于程序员本身的。

可执行代码与源代码分离

https://labview.qizhen.xyz/

我在书中和之前的文章中介绍到了LabVIEW的一个与其它语言不同之处:它把程序源代码和编译好的可执行代码存储在了同一个文件中。我一直觉得这并不是一个好的方法。对于普通LabVIEW用户,这也许不是一个严重问题,甚至很多人都不需要关心LabVIEW是怎么保存文件的,只要他的程序能正确运行就万事大吉。

但是,源代码与可执行代码混合存储的弊病还是给我的开发带来了一定影响。我开发的项目较大,有多个人同时参与开发,这样一来,同一个VI文件就会在不同的开发者之间传来传去。每个开发者有自己的喜好,有人有64-bit的LabVIEW进行开发,有人用32-bit的LabVIEW,有人用Windows,有人用Linux。这本来不应该是一个问题,LabVIEW支持跨平台,相同的源代码不加修改就可以在任何平台下运行。

问题在于不同平台下编译出来的可执行代码是不同的,我的程序复制到同事的电脑上,即便代码不做任何修改,VI也会被修改,因为VI中包含的可执行代码变了。这样,我就没法简单的判断一个VI是否被同事做了修改,还是仅仅编译后的代码变了,非常不利于程序源代码管理。

这个问题可以通过分离VI的源代码与可执行代码来解决,这是LabVIEW2010开始出现的功能。在VI的属性对话框中,有一个选项是“分离编译后代码与源代码”。若这一选项被选中,则VI中只保留程序的源代码,而编译生成的代码则被移出VI之外。

image

被分离出来的编译好的可执行代码又LabVIEW统一管理,在我的Windows 7 系统中,文件夹C:\Users\[user name]\Documents\LabVIEW Data\VIObjCache\[LabVIEW version number]\ 下有一个objFileDB.vidb 文件,这就是用来存储LabVIEW 程序所有可执行代码的数据库。

若程序会在不同平台下协同开发,或者程序会被发布到不同平台下去,都可以考虑采用可执行代码与源代码分离的策略,避免不必要的VI变动。

理论上,可执行代码与源代码分离开来,可以提高程序的加载速度:程序可以各取所需,只加载源代码或只加载可执行代码。不过我并没有太明显的感觉到速度的变化。