对
WebAssembly (Wasm)技术的支持已在最近的浏览器中出现。 但是,这项技术可以很好地扩展Web的功能,使其成为能够支持通常被视为桌面的此类应用程序的平台。
掌握WebAssembly对于Web开发人员而言可能是一项艰巨的任务。 但是,
AssemblyScript编译器可以改善这种情况。
该文章的作者(我们将在今天翻译该文章)首先讨论WebAssembly为什么是一项非常有前途的技术,然后再看看AssemblyScript如何帮助发挥Wasm的潜力。
网络组装
WebAssembly可以称为浏览器的低级语言。 它使开发人员能够创建可编译为JavaScript之外的代码。 这使网页中包含的程序几乎可以与各种平台的本机应用程序一样快地运行。 此类程序在有限的安全环境中运行。
负责开发所有领先的浏览器(Chrome,Firefox,Safari和Edge)的团队的代表参与了WebAssembly标准的创建。 他们在2017年初商定了系统
架构 。 现在,以上所有浏览器都支持WebAssembly。 实际上,该技术可以在
大约87%的浏览器中使用。
WebAssembly代码以二进制格式存在。 这意味着此类代码比类似的JavaScript代码小,并且加载速度更快。 此外,Wasm代码可以
文本格式显示 ,以便人们可以阅读和编辑它。
当WebAssembly标准首次出现时,一些开发人员认为它很可能取代JavaScript并成为Web的主要语言。 但是,最好将WebAssembly看作是一种与现有Web平台很好地集成的新工具。 这是他的
首要目标之一 。
WebAssembly不会代替已经使用了很长时间并且成功使用该语言的JavaScript,而是为Web开发人员提供了新的有趣机会。 的确,Wasm代码无法直接访问DOM,因此大多数现有的Web项目将继续使用JavaScript。 经过多年的开发和优化,这种语言已经相当快了。 WebAssembly有其自己的
应用程序 :
- 游戏类
- 科学计算,可视化,模拟。
- CAD应用程序。
- 编辑图像和视频。
Wasm的所有这些用途通常与它们各自的应用程序被视为台式机结合在一起。 但是由于WebAssembly可以使您达到接近本机的性能水平,因此现在可以使用Web平台实现许多类似的应用程序。
WebAssembly可以利用现有的Web项目。
Figma项目就是一个例子。 由于使用了Wasm,该项目的加载时间大大缩短了。 如果网站使用执行繁重计算的代码,则为了提高该网站的性能,仅将此类代码替换为WebAssembly类似物是有意义的。
您可能要尝试在自己的项目中使用WebAssembly。 可以立即学习并
在上面写
上这种语言。 但是,尽管如此,WebAssembly最初是作为其他语言的
编译目标而开发的。 它的
设计具有对C和C ++的良好支持。 Wasm
实验支持出现在Go 1.11中。 在
Rust中编写Wasm应用程序需要付出很多努力。
但是,Web开发人员完全有可能不想仅仅使用WebAssembly就学习C,C ++,Go或Rust。 他们做什么? 这个问题的答案可以给出AssemblyScript。
AssemblyScript
AssemblyScript是将TypeScript代码转换为WebAssembly代码的编译器。 TypeScript是Microsoft开发的一种语言。 这是JavaScript的超集,具有改进的类型支持和其他一些功能。 TypeScript已成为一种相当
流行的语言。 应当注意,AssemblyScript只能将有限的TypeScript构造集转换为Wasm。 这意味着,即使不熟悉TypeScript的人也可以以足以编写AssemblyScript可以理解的代码的水平快速学习该语言。
此外,鉴于TypeScript与JavaScript非常相似,我们可以说AssemblyScript技术使Web开发人员可以轻松地将Wasm模块集成到他们的项目中,而无需学习一种全新的语言。
例子
让我们编写第一个AssemblyScript模块。 我们现在要讨论的所有代码都可以在
GitHub上
找到 。 为了
支持 WebAssembly,我们至少需要
Node.js 8。
创建一个新目录,初始化npm项目并安装AssemblyScript:
mkdir assemblyscript-demo cd assemblyscript-demo npm init npm install --save-dev github:AssemblyScript/assemblyscript
请注意,必须直接从项目的
GitHub存储库中安装AssemblyScript。 AssemblyScript尚未在npm中发布,因为开发人员
尚未考虑将其准备好广泛使用。
我们将使用
asinit
包含的
asinit
命令创建辅助文件:
npx asinit .
现在,
package.json
的
scripts
部分应采用以下形式:
{ "scripts": { "asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --sourceMap --validate --debug", "asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --sourceMap --validate --optimize", "asbuild": "npm run asbuild:untouched && npm run asbuild:optimized" } }
位于项目根文件夹中的
index.js
文件将如下所示:
const fs = require("fs"); const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/optimized.wasm")); const imports = { env: { abort(_msg, _file, line, column) { console.error("abort called at index.ts:" + line + ":" + column); } } }; Object.defineProperty(module, "exports", { get: () => new WebAssembly.Instance(compiled, imports).exports });
这使您可以使用
require命令在代码中包含WebAssembly模块。 即-与连接常规JavaScript模块相同。
assembly
文件夹包含
index.ts
文件。 它具有根据AssemblyScript规则编写的源代码。 自动生成的样板代码是添加两个数字的简单功能:
export function add(a: i32, b: i32): i32 { return a + b; }
也许您希望类似功能的签名看起来像
add(a: number, b: number): number
。 因此,它看起来是否是用普通的TypeScript编写的。 但是在这里,
i32
类型代替
number
类型。 发生这种情况是因为AssemblyScript代码将特定的
WebAssembly类型用于整数和浮点数,而不是TypeScript中的通用
数字类型。
让我们组装项目:
npm run asbuild
以下文件应出现在
build
文件夹中:
optimized.wasm optimized.wasm.map optimized.wat untouched.wasm untouched.wasm.map untouched.wat
有装配的优化版本和常规版本。 程序集的每个版本都会为我们提供一个二进制.wasm文件,一个
map map .wasm.map以及.wat文件中二进制代码的文本表示形式。 Wasm代码的测试演示文稿是供程序员使用的,但我们什至不会查看此文件。 实际上,使用AssemblyScript的原因之一是它消除了使用Wasm代码的需要。
现在让我们在REPL模式下运行Node.js,并确保可以将已编译的Wasm模块以与任何常规JS模块相同的方式使用:
$ node Welcome to Node.js v12.10.0. Type ".help" for more information. > const add = require('./index').add; undefined > add(3, 5) 8
通常,这是在Node.js中使用WebAssembly技术所需的全部。
为项目配备观察者脚本
为了在开发过程中对其进行更改时自动重建模块,建议使用
onchange软件包。 事实是AssemblyScript
还没有自己的系统来监视文件更改。 安装onchange软件包:
npm install --save-dev onchange
将
asbuild:watch
脚本添加到
package.json
。
-i
标志包含在命令中,以便在调用脚本之前(在发生任何事件之前)构建过程开始一次。
{ "scripts": { "asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --sourceMap --validate --debug", "asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --sourceMap --validate --optimize", "asbuild": "npm run asbuild:untouched && npm run asbuild:optimized", "asbuild:watch": "onchange -i 'assembly/**/*' -- npm run asbuild" } }
现在,不再运行
asbuild
命令,而是运行
asbuild:watch
一次。
性能表现
我们将编写一个简单的测试,以评估Wasm代码的性能水平。 WebAssembly的主要范围是解决密集使用处理器的任务。 例如,这些是某种“繁重”的计算。 让我们创建一个函数,确定某个数字是否为质数。
相似功能的基本JS实现如下所示。 它的布置非常简单,它通过蛮力检查数字,但是出于我们的目的,它很适合,因为它执行了大量的计算。
function isPrime(x) { if (x < 2) { return false; } for (let i = 2; i < x; i++) { if (x % i === 0) { return false; } } return true; }
为AssemblyScript编译器编写的类似函数看起来几乎相同。 主要区别在于代码中存在类型注释:
function isPrime(x: u32): bool { if (x < 2) { return false; } for (let i: u32 = 2; i < x; i++) { if (x % i === 0) { return false; } } return true; }
为了分析代码的性能,我们将使用
Benchmark.js包。 安装它:
npm install --save-dev benchmark
创建
benchmark.js
文件:
const Benchmark = require('benchmark'); const assemblyScriptIsPrime = require('./index').isPrime; function isPrime(x) { for (let i = 2; i < x; i++) { if (x % i === 0) { return false; } } return true; } const suite = new Benchmark.Suite; const startNumber = 2; const stopNumber = 10000; suite.add('AssemblyScript isPrime', function () { for (let i = startNumber; i < stopNumber; i++) { assemblyScriptIsPrime(i); } }).add('JavaScript isPrime', function () { for (let i = startNumber; i < stopNumber; i++) { isPrime(i); } }).on('cycle', function (event) { console.log(String(event.target)); }).on('complete', function () { const fastest = this.filter('fastest'); const slowest = this.filter('slowest'); const difference = (fastest.map('hz') - slowest.map('hz')) / slowest.map('hz') * 100; console.log(`${fastest.map('name')} is ~${difference.toFixed(1)}% faster.`); }).run();
这是在计算机上运行
node benchmark
命令后获得的信息:
AssemblyScript isPrime x 74.00 ops/sec ±0.43% (76 runs sampled) JavaScript isPrime x 61.56 ops/sec ±0.30% (64 runs sampled) AssemblyScript isPrime is ~20.2% faster.
如您所见,该算法的AssemblyScript实现比JS实现快20%。 但是,请注意,此测试是一个微
基准测试。 不要过分依赖其结果。
为了找到AssemblyScript项目性能研究的更可靠结果,我建议您看一下
该基准和
本基准。
在网页上使用Wasm模块
让我们在网页上使用Wasm模块。 我们首先创建一个具有以下内容的
index.html
文件:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>AssemblyScript isPrime demo</title> </head> <body> <form id="prime-checker"> <label for="number">Enter a number to check if it is prime:</label> <input name="number" type="number" /> <button type="submit">Submit</button> </form> <p id="result"></p> <script src="demo.js"></script> </body> </html>
现在创建
demo.js
文件,其代码如下所示。 有
许多加载WebAssembly模块的方法。 最有效的方法是使用
WebAssembly.instantiateStreaming函数以流模式编译和初始化它们。 请注意,我们将需要重新定义
中止函数,如果未执行某些
语句 ,则会调用该函数。
(async () => { const importObject = { env: { abort(_msg, _file, line, column) { console.error("abort called at index.ts:" + line + ":" + column); } } }; const module = await WebAssembly.instantiateStreaming( fetch("build/optimized.wasm"), importObject ); const isPrime = module.instance.exports.isPrime; const result = document.querySelector("#result"); document.querySelector("#prime-checker").addEventListener("submit", event => { event.preventDefault(); result.innerText = ""; const number = event.target.elements.number.value; result.innerText = `${number} is ${isPrime(number) ? '' : 'not '}prime.`; }); })();
接下来,安装
静态服务器软件包。 由于要使用
WebAssembly.instantiateStreaming
函数,必须使用
application/wasm
MIME类型为模块提供服务,因此我们需要服务器。
npm install --save-dev static-server
将适当的脚本添加到
package.json
:
{ "scripts": { "serve-demo": "static-server" } }
现在,
npm run serve-demo
命令并在浏览器中打开本地主机URL。 如果在表格中输入某个数字,则可以确定它是否简单。 现在,在开发AssemblyScript方面,从编写代码到在Node.js和网页上使用它,我们已经走了很长一段路。
总结
WebAssembly以及AssemblyScript无法以某种方式神奇地加速任何站点。 但是Wasm从来没有承担过绝对加速所有任务的任务。 该技术的卓越之处在于它为新型应用打开了通往网络的道路。
关于AssemblyScript,可以说类似的话。 这项技术简化了许多开发人员对WebAssembly的访问。 当使用接近JavaScript的语言创建代码时,它允许使用WebAssembly的功能来解决复杂的计算问题。
亲爱的读者们! 您如何评估在项目中使用AssemblyScript的前景?
