使用WXWebAssembly优化运算性能 您所在的位置:网站首页 微信小程序怎么发QQ浏览器 使用WXWebAssembly优化运算性能

使用WXWebAssembly优化运算性能

2023-04-20 15:54| 来源: 网络整理| 查看: 265

你好,我是李艺。

上节课我们主要学习了代码的分布异步化以及插件代码的分布异步化。这节课我们学习如何使用WebAssembly技巧以及如何优化代码的执行性能。

首先我们看一个问题,WebAssembly号称是Web的汇编,它允许开发者使用Go C C++等后端强类型语言编写代码,然后将其编译为一种类似于汇编代码的二进制中间代码,使用这种代码可以在Web页面里边直接运行,汇编语言的性能是毋庸置疑的,这项技术从根本上一举弥补了解析型语言JS在执行性能上的一个不足,小程序已经添加了WXWebAssembly功能,实现了对WebAssembly技术的一个全面基础的支持,接下来我们看项目实践。

首先看实践一,使用Go语言编译WebAssembly的文件。

首先第一步我们需要编写Go语言的逻辑代码,在我们源码里面有一个叫做main.go这样的一个文件,这个文件它的一个主要作用是代替我们原来的stopwatch组件里面内嵌函数,叫做convertTimeStampToString这个函数的功能,Go语言在这个时间操作上,它有标准库方法支持,比较完善,已经不需要像JS代码那样进行时间字符串的一个拼接了,第二步编译和压缩wasm文件,编译以及压缩文件的脚本在我们最终源码里面已经写好了,这个编译脚本是build.sh,压缩脚本是compress.sh 直接取用就可以了,第三步我们要从Go语言这个源码里面拷贝并且修改一个wasm_exec.js这样的一个文件,这是让这个wasm文件与这个JS协同工作的一个桥梁,它是一个必不可少的一个文件。

源码里面有一个叫做copyjs.sh这样的一个脚本,我们使用这个脚本就可以从Go语言源码包里面拷贝出wasm_exec.js这个文件了,这里有一个问题需要注意一下,在这个小程序里面使用的时候,wasm_exec.js这个文件的源码里面有些地方必须要修改,但是这些你已经不需要做了,因为修改之后的文件我也已经放在源码里面了,还有就是同时也需要一个text_encoder.js文件,它是这个wasm_exec.js文件执行的时候需要的一个模块,在使用的时候直接拷贝到我们小程序项目里面就可以了,这里有一个非常重要的点需要注意,如果你安装的Go语言版本不是go1.17.8这个版本的话,那么就很有可能不能用我修改过的JS文件,因为这个文件它与版本是高度绑定的,换一次版本你就要修改一次。在源码里面还有一个叫做index.html这个文件,这是一个测试文件,我们运行这个文件以后可以验证编译出来的WebAssembly的文件结果是否正常。

下面我们看实践一的代码演示。

首先我们需要在我们这个项目的根目录下创建一个新的目录,用于放我们Go语言代码,我们这个目录可以起名叫做go_stopwatch,下面的事情我们要看一下我们已经编译好的最终代码了,第一个我们要拿来取用的一定是我们的Go语言的代码,main.js 这是我们第一个要启用的代码,这个文件我们可以拷贝一下放在我们的目录下面,这个文件我们可以简单看一下 它主要的一个代码,首先这个地方是一个package的一个声明,它声明我们这个包的一个名字,这个是不可或缺的一个,再往下是import 这个是模块的一个引入,我们现在使用的都是标准库的一个模块 没有第三方的,这个地方是一个zero 一个临时对象,下面我们要使用它,因为它没有必要重复创建,所以把它作为一个模块变量放在这个地方。

这个地方有一个getFormatedMiniSeconds,这个方法是我们将一个一个ts 一个时间 一个值,一个ms 一个时间值传进来以后,用Go语言进行一个格式化,格式化成这样的一种格式,在Go语言里面我们注意一下它的格式化字符串很特别,它是拿指定的时间的字符串 然后那种格式、去作为一个格式化字符串的,就是这个地方,比如说04:05这个数字我们不可以改,它就是默认作为一个特殊的字符串进行存在的,调用方式跟我们一般的Go语言的调用方式有点不一样,因为我们这个地方,本身我们参数的接收是从JS语言里面接收的,然后它返回也是向JS语言进行返回的,我们看到这个地方接收,首先this对象,第一个this对象,它的值是js.Value,而js.Value类型其实是在syscall/js标准库里面,它是这样引用出来的。

本身这个功能就是Go语言支持的这种WebAssembly文件编译的这种功能,也是官方的标准库支持的,并没有第三方的类库的参与,再往下面是args这是我们从JS传递过来的一个参数,它是一个数组,我们在这个地方取到了它的参数,第一个是ms,也就是一个毫秒的字符串,第二个是我们的一个回调函数,然后在这个地方我们是先计算,计算完成以后把计算出来这个结果传给callback,也就是JS里面的一个回调函数,传给它。

这个地方我们还用了协程,这个是Go语言里面协程的一个写法就是go func,然后后面跟一个函数然后就可以执行了,它就新开一个这样的一个协程并执行,这是Go语言里面的一个主函数 就是main,在这个里面我们首先是创建了一个channel 一个通道,这是一个需要从JS里面去获取它的console的这样的一个接口,因为我们要用它里面console.log这样一个方法,第二步我们获取了 不是获取 这是设置,我们这是Set,Get是获取,Set了一个方法,就是getFormatedMiniSeconds,这个方法稍后我们在JS里面会调用。

最后一步这个地方我们有一个阻塞这个程序退出的通道,就是有这样的一个通道读写,尝试从里面去读,但其实它里面没有东西 是读不出来的,所以它这个程序会一直停在这个地方,这是防止我们程序退出,如果你退出的话,在JS里面你就调不到这个里面设置的方法了,这就是我们Go语言的一个代码,它作为一个示例十分简单,但是它已经演示了我们如何去使用Go语言去编写一个WebAssembly这样的一个文件。

接下来我们还需要做一些工作,首先是我们要将这几个脚本文件给它拿过来,build.sh这些脚本文件一共是三个,把它给拷贝一下然后放过来,为了节省时间 所以我们脚本其实不用去手敲代码了,当然你们自己如果想练习的话也可以手敲代码。首先是build 这个代码是这样的,你在执行的时候第一步一定要先安装Go语言的语言包,并且这个版本要和我保持一致,不保持一致的后果前面我们也提到了对吧,它可能会遇到一些未知的问题,你可能有一些额外的工作需要做,这个是比较麻烦的,因为我之前已经做过了,这个脚本出来以后,接下来我们要做一个事情就是调用了,调用之前首先我们要把环境变量给它关一下,这个是它的模块的一个开关,先给它关上,关上以后我们就可以调用我们build.sh了。

脚本大概的意思它会编译我们main.go这个文件,然后编译成一个stopwatch.wasm这样的一个文件,然后执行,执行成功了,这是我们编译出来的一个文件,编译出来以后还没完,为啥还没完,因为在这个小程序里面提供了对br压缩文件这种功能的一个支持,因为这个文件它编译以后文件大小比较大,直接放到我们小程序代码包里面直接打包的话,其实会占很大的一个代码包额度,所以这个地方我们最好要进行一次压缩,压缩的话其实也很简单,我们需要使用一个叫做brotli的这样的一个模块,用这个模块对它进行压缩,这个模块如果你本地还没有的话,需要先用这个brew然后进行安装,当然如果包管理工具你没有的话,可以用上面这个指令进行先安装一下,安装以后,再安装下面的模块,安装以后然后就可以去压缩这个文件了。压缩的时候是这样,我们先把目标的位置,这是我们要放的位置,如果有的话先给它删掉,删掉以后,接下来就调它的brotli这个模块 ,然后去以我们当前的文件为基础进行压缩,废话不多说,我们就运行一下我们这个代码,已经执行完了。

我们看一下我们这个目标目录下面这个地方,在.go这个就是我们的目标文件,但是因为我们原来的它这个地方也有一个文件,所以我们可以先将它删掉,删掉以后然后再看,已经有了对吧,这是压缩 压缩以后,另外还有一个文件需要处理就是copyjs.sh,这个脚本它本质是从我们当前系统里面安装源,就是Go语言的源码里面去它的指定位置去拷贝这样的一个文件,然后拷贝到当前的目录下,当然这个脚本我现在不要去执行它,因为我本地的文件我已经做过修改了,拷贝以后如果你自己要做修改的话,我告诉你有哪些需要修改,可以拿这个IsWechat去搜索一下当前这个文件里面,你看有很多地方吗,这些地方都是要修改的,并且如果你的Go语言版本跟我这个不太一样的话,它导出来的文件也不一样,所以这就是我们前面说的,你最好与我的版本保持一致,如果是一致的话 这个文件你没必要修改了,直接拿我这个文件用就可以了。

这个地方我们还有一个web脚本,这个脚本是它原生的一个脚本,用于这个地方,我们把这个IsWechat改成false了,这个文件我们可以用在我们的另外一个测试文件里面,就是index.html,加载JS脚本,然后在下面我们这个地方有一个JS代码去加载我们这个文件对吧,编译好的这个wasm这个文件加载以后,又在这个地方去调用了这个里面的方法getFormatedMiniSeconds,这个方法就是刚才我们在Go语言里边去输出的方法、导出的方法,所有的这些文件现在这个 还有这个JS 这个 这个,这几个文件我们一起来拷贝一下,放在我们的这个地方,放进来了,然后在我们目录下面为了测试我们工程链的一个可行性,稍微可以对我们这个代码做一点点的一个修改,做一点点的修改,怎么修改,比方说我们这个地方有一个res,res是个变量,假如我们把它改一下的话,res等于,算了我们不改这个了,如果我们要用什么的话,可能还需要去添加新的模块,所以我们就拼接就可以了 我们随便给它加个后缀 加个st就好了,或者加个ms。

加完以后,第一步我们要先编译我们这个代码,build先编译,编译以后然后进行一个压缩,压缩完成以后,其实这个地方是可以通过看它的时间来判断它是不是我们修改过后的一个文件,比方说我们在这个目录,这是我们的目标目录,在资源管理器里面打开,然后你看一下它这个时间7:52对不对,这就是我们刚才发生的时间,它已经被修改了,接下来我们测试 怎么测试,首先我们要看一下这个文件正不正常,这个地方有一个专门用于测试的文件,这个文件我们可以拿一个插件Live Server,Live Server是我安装的,你可以在VSCode里面进行安装,我们在浏览器里面打开这个文件,然后打开调试区看一下这个地方 看到没有,get res 然后后面有一个时间字符串,并且后面多了一个ms,因为ms是我们刚才新加的对吧,调用结果说明我们这个测试的代码是没有问题的。

接下来我们开始搞 再往下检查一下我们这些文件,在这个目录下br文件已经有了对吧,text_encoder文件也有了,然后wasm_exec.js这个文件有了,并且这个里面我们可以看到它前面IsWechat等于true,正是我们要在小程序里面使用的这个版本,代码演示我们就说到这里。

接下来我们看实践二,在小程序里面创建和使用stopwatch_go组件。

第一步首先我们需要将相关的这个JS文件,wasm文件等这些文件全部拷贝到我们目录下面,与我们组件放在一起,并且在这个目录下面要依照原来的stopwatch组件的代码创建新的组件,stopwatch_go组件的JS代码就是在我们原来组件的基础之上然后修改过来的,在这个组件里面有一个特殊的flag变量,这个变量的作用是节制我们对WebAssembly文件的调用,为什么要节制呢,因为跨程序文件之间的调用,它与这个设备性能有很大的关系,我们并不能保证调用之后多久可以拿到返回的结果,所以这个地方我们一定要有节制,stopwatch_go组件的wxss样式代码还有它的wxml这个标签代码与我们原来组件是一样的 没有变化,直接拷贝过来就可以使用了,第二步就是我们要修改主页配置里面对组件的一个使用,开始使用新组件然后进行测试,从最终的一个测试结果来看效果还是不错的,下面我们开始代码演示。

下面我们开始实践二的代码演示。

目前在我们这个项目里面的wasm.br组件已经准备好了 我们看一下,在这个目录下 这个文件就是我们新编译的一个文件 对吧,这个组件代码已经有了,当它是从我们最终源码里面拷贝过来就可以了,简单看一下wxml标签代码跟之前是一样的,样式代码也没有变化,然后在JS文件里面 这个文件里面我们要看一下,重点是在这里面,这个地方我们引入了一个wasm_exec.js这个文件,这个文件就是先前我们拷贝过来的对吧,放在这个组件目录下的,然后第二个是我们做了一个路径,就是这个文件的一个路径,wasm.br这个文件的一个路径,这个路径不能使用相对地址,必须使用绝对地址,再往下,上面这些都是一样的,然后在我们的methods里面有一个intGo初始化,因为在使用之前我们要初始化,初始化我们在哪里调用 在lifetimes是我们组件的生命周期,它的这样一个函数组,其中里面有个ready是周期函数,在这个ready里面是组件准备好以后我们要执行代码,这个地方我们看global.console等于console,这个里面是去命名我们Go语言里面设的打印的代码接口,设置接口以便Go语言可以调用。

这个地方我们用了await,然后去等待我们的初始化代码的一个完成,因为它如果初始化不完成的话,你调用它是没有作用的,必须等它初始完成,而且这个初始化它不是很快就可以完成,这个地方是访问从global.Go上面,这个是全局的对象,从上面然后去访问它再调用WXWebAssembly,instantiate这个是小程序它本身提供的接口方法去实例化这个文件,实例化以后,其实这个代码它执行到这个位置,可以认为它第一次执行已经结束了,然后其实它不会有一个结果,它如果要有结果的话就是我们Go语言main那个方法它退出了,如果它退出的话接下来你去调用JS 调用Go里面那个方法就调用不到了,所以这个地方初始化其实它只是执行了一次,执行了第一次那个方法,后面那个是走不到的。

再往下就是start stop还有switch,stop和switch没有什么变化 和原来是一样的,重点是start里面 在这个里面我们有变化,这个地方,这个名字还是原来的名字,这个地方我们去直接在global上面调了getFormatedMiniSeconds,这个方法其实是Go语言通过WebAssembly,通过那个文件然后写到全局对象上的,所以我们在这个地方可以调用,把ts传给它,然后拿到这个res 就是我们那个结果,再通过setData然后进行视图的一个渲染。

这个地方注意有一个flag,我们刚才提到了我们要加一个flag,为什么加一个flag 就是为了标识一下每次调用成功以后,我们才可以进行第二次的一个调用,如果是上一次调用没有回来 你这次不要去急着去调用,如果你调用多的话,CPU 内存可能就吃不消了,程序可能就卡死了,所以这个地方我们看到这有一个flag,它每次有一个循环就是一个定时器,然后去调用,这样的一个结果,这就是这个代码。

接下来我们开始修改我们调用代码,开始去使用我们写的组件,在我们index文件里面看一下这个地方把这个组件的地址改成_go 对不对,只要改这一个地方就可以了,其他地方其实不需要修改,然后编译,有的时候组件它是不显示的,但是我们需要有一点点的耐心,可以清一下编译缓存然后重新再点一下编译按钮,是这样的,刚才我把index_addons分包给它删掉了,因为之前测试它老是加载不到这个组件,所以我们把分包给它去掉,分包去掉以后组件,我们index_addons这个目录其实现在已经属于主包了,所以我们现在把它去掉以后,它其实可以更优先被加载,优先被注入和使用。

现在这个文件,这个组件已经显示了,然后我们尝试去单击这个组件,看到没有,有变化,后面这有一个ms,原本那个文件里面是没有了ms是我们刚才新加的,而且看起来好像很流畅,具体流不流畅,其实无法靠我们的视觉进行评判,因为视觉会产生误差,特别是同样的一个代码,如果你在你自己的机器上运行和你同事的机器上运行,可能它这个结果,产生性能的表现它也是不一样的,比较公平的一个方式就是我们查看Performance面板,让它启动,然后打开Performance面板的一个录制按钮,等到执行几次以后单击停止,这一段已经停止了所以它后面没有,然后这一段是执行时间,我们可以看到这个性能是相当的喜人,总执行时间只有7ms 7毫秒,看见没有,厉害,Go语言果然很强大,这个地方18毫秒,所以它这么短的时间根本就没有红三角,这个时间其实是反应是很快的,从直观上来看,单击以后你看这个很流畅,然后再单击停止也很流畅,所以这个效果还是很不错的。

当然这个跟我们做了一些优化也有关系,因为我们前面提到了,这个文件里面我们加了一个flag,如果你没有这个flag的话,你认着这个定时器,然后一直往Go语言的那个wasm文件里面去发请求的话,它如果处理不过来,你积压这个请求越来越多,最后你这个内存它会一直飙升,然后拖到最后可能就把你这个程序给拖垮,所以在合适的节奏里面去调用这个代码是相当重要的。我们代码演示就说到这里。

最后我们来总结一下,Go语言是强类型编译语言,它被称作是互联网时代的C语言,根据2022年3月份腾讯发布的研发报告,Go语言在腾讯内部项目里面的使用已经超过了C++,成为腾讯后台编程语言里面的第一选择。我们在Go语言里边还使用了协程,还有就是我们这个代码实例其实逻辑也十分简单,所以我们一点也不怀疑我们Go语言代码的执行效率,那么在这种情况下,为什么还要在这个小程序里面,在调用这个wasm这个文件的时候还要加上这个调用限制,Go语言它执行效率虽然很高,但是JS和Go语言之间相互调用中间还有一个wasm_exec.js这个文件,这个文件它是无法跨过的一个瓶颈,所以WebAssembly这种技术适合做大块数据的一个运算,运行一次相比JS执行可以节省很多时间,但其实它并不适合高频运算这样的一个需求场景。这节课我们就讲到这里,现在屏幕上显示的网址是我们本节课所涉及的文档地址。

点击查看开放文档:

性能与体验 /WXWebAssembly

这节课我们主要学习到如何使用Go语言编写代码、如何编译为这个wasm文件以及如何在这个小程序里面使用WebAssembly的技术。下节课我们学习异步转同步的编程范式。

最后我们看一下思考题,这里有一个问题请你思考一下,就是异步编程它是JS的一大特点,但是太多的回调函数一层嵌套一层又使得这个代码看起来逻辑非常的不清晰,非常不利于维护,那么有没有一种办法既可以享有异步编程的无阻塞优点,又能保持我们这个代码的清晰连贯性,这个问题先留给你思考一下。下节课我们一起来深入探讨一下这个问题。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有