写点什么

AssemblyScript 如何帮助 WebAssembly 发挥潜力?

2019 年 12 月 04 日

AssemblyScript如何帮助WebAssembly发挥潜力?

WebAssembly(或 Wasm)是 Web 浏览器中相对较新的功能,但它有潜力极大地扩展 Web 作为一个应用程序服务平台的能力。Web 开发人员在入门 WebAssembly 时可能会经历艰难的学习过程,而AssemblyScript就提供了一种解决办法。首先我们来看一下为什么 WebAssembly 是一项很有前途的技术,然后再介绍 AssemblyScript 是怎样帮助 WebAssembly 发挥潜力的。


WebAssembly

WebAssembly 是针对浏览器使用的底层语言,为开发人员提供了 JavaScript 之外的 Web 编译目标。它使网站代码可以在安全的沙盒环境中以接近原生的速度运行。


它是根据所有主流浏览器(Chrome、Firefox、Safari 和 Edge)代表的意见开发的,这些代表于 2017 年初达成了设计共识。所有这些浏览器现在都支持 WebAssembly,意味着整个市场中约 87%的浏览器可以使用它。


WebAssembly 以二进制格式交付,这意味着与 JavaScript 相比,WebAssembly 在大小和加载时间上均占优势。但它也有供人类阅读的文本表示形式


当 WebAssembly 首次亮相时,一些开发人员认为它最后有可能取代 JavaScript,成为 Web 的主要语言。但最好将 WebAssembly 视为与现有 Web 平台集成良好的一项新工具,这也是其高阶目标之一


WebAssembly 并没有取代现有的 JavaScript 用例,而是开拓了新的用户场景,吸引了更多人的兴趣。WebAssembly 尚不能直接访问 DOM,并且大多数现有网站都希望继续使用 JavaScript——毕竟经过多年的优化,JavaScript 已经相当快了。下面是 WebAssembly 自身提供的可行用例列表:


  • 游戏

  • 科学计算的可视化和模拟

  • CAD 应用

  • 图像/视频编辑


这些用例的共同属性是,我们通常会将它们视为桌面应用程序。WebAssembly 可以为 CPU 密集型任务提供接近原生平台的性能表现,这样人们就能将更多桌面型应用程序移植到 Web 端了。


现有网站也可以从 WebAssembly 中受益。Figma提供了一个现实应用的示例,它使用 WebAssembly 大大缩短了加载时间。如果网站使用的某些代码需要进行大量的计算,则可以只将这部分代码替换为 WebAssembly 以提高性能。


所以也许现在你就有兴趣开始使用 WebAssembly 了。你可以学习这种语言本身并直接编写它,但实际上它打算成为其他语言的编译目标。它被设计为对 C 和 C++具有良好的支持,Go 在 1.11 版中添加了对它的实验性支持,Rust 也对其投入了大量资源


但也许你并不想为了使用 WebAssembly 而学习或使用其中的任何一种语言。这就轮到 AssemblyScript 出场表现了。


AssemblyScript

AssemblyScript 是一个 TypeScript 到 WebAssembly 的编译器。由 Microsoft 开发的 TypeScript 为 JavaScript 添加了类型。它已经非常流行了,但就算用户不怎么熟悉 TS,AssemblyScript 也只支持 TypeScript 功能的一个有限子集,因此不需要花很长时间就能上手。


因为它与 JavaScript 非常相似,所以 AssemblyScript 使 Web 开发人员可以轻松地将 WebAssembly 整合到他们的网站中,而不必使用某种完全不同的语言。


尝试一下

下面我们试着编写第一个 AssemblyScript 模块(所有代码都在这个 GitHub 仓库中提供:https://github.com/dguo/assemblyscript-demo)。为了支持 WebAssembly,我们需要的Node.js最低版本是8.0


转到一个空目录,创建一个 package.json 文件,然后安装 AssemblyScript。请注意,我们需要直接从其 GitHub 仓库安装它。它尚未在 npm 上发布,因为 AssemblyScript 开发人员认为这个编译器尚未准备好应对一般用途。


mkdir assemblyscript-democd assemblyscript-demonpm initnpm install --save-dev github:AssemblyScript/assemblyscript
复制代码


使用随附的 asinit 命令生成脚手架文件:


npx asinit .
复制代码


我们的 package.json 现在应该包含以下脚本:


{  "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 模块,就像 require 普通的 JavaScript 模块一样。其中,assembly 目录包含我们的 AssemblyScript 源代码。生成的示例是一个简单的加法函数。


export function add(a: i32, b: i32): i32 {  return a + b;}
复制代码


你可能以为函数签名会像 TypeScript 中的形式,也就是 add(a: number, b: number): number 这种格式;但这里之所以使用 i32,原因是 AssemblyScript 使用了 WebAssembly 的特定整数和浮点类型,而不是 TypeScript 的通用数字类型。下面我们来构建示例。


npm run asbuild
复制代码


现在,build 目录应包含以下文件:


optimized.wasmoptimized.wasm.mapoptimized.watuntouched.wasmuntouched.wasm.mapuntouched.wat
复制代码


我们得到了构建的普通版本和优化版本。对于每个构建版本,我们都有了一个.wasm 二进制文件、一个.wasm.map源映射,以及该二进制文件的.wat 文本表示形式。文本表示形式是用来供人类阅读的,但在这个例子中我们无需阅读或理解它——使用 AssemblyScript 的其中一个目的,就是用不着使用原始的 WebAssembly 了。


启动 Node,并像其他模块一样使用我们的编译模块。


$ nodeWelcome to Node.js v12.10.0.Type ".help" for more information.> const add = require('./index').add;undefined> add(3, 5)8
复制代码


从 Node 调用 WebAssembly 就只需要这些步骤!


添加监视脚本

在开发时,建议你在更改源代码时使用onchange自动重建模块,因为 AssemblyScript 尚不包含监视模式


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:watch,这样就用不着不断重新运行 asbuild 了。


性能

我们来写一个基本的基准测试,看看我们可以获得怎样的性能提升。WebAssembly 的专长是处理诸如数字计算之类的 CPU 密集型任务,所以我们这里使用一个函数来确定一个整数是否为质数。


我们的参考实现如下所示。这是一种原始的暴力解决方案,因为我们的目标是执行大量计算。


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(https://benchmarkjs.com/)。


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;
复制代码


在我的机器上,运行 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.
复制代码


请注意,这个测试是一个microbenchmark,我们不应该太看重它的结果。


如果你想要参考一些更深度的 AssemblyScript 基准测试,我建议了解WasmBoy基准测试wave equation基准测试


加载模块

接下来我们在网站中使用我们的模块。创建 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.`; });})();
复制代码


现在安装static-server。我们需要一个服务器,因为使用 WebAssembly.instantiateStreaming 时,该模块需要 MIME 类型的 application/wasm。


npm install --save-dev static-server
复制代码


将脚本添加到 package.json。


{  "scripts": {    "serve-demo": "static-server"  }}
复制代码


运行 npm run serve-demo 命令,并在浏览器中打开 localhost URL。在表单中提交一个数字,你将收到一条消息,指出该数字是否为质数。到这里,从编写 AssemblyScript,到在网站中实际使用它的整个流程我们都走了一遍。


结论

WebAssembly 和它的 AssemblyScript 扩展并不会一夜之间加快所有网站的速度,但这也不是它们的目的。WebAssembly 之所以令人兴奋,是因为它为 Web 开拓了更多的可能性,从而支持更多种类的应用程序。


类似地,AssemblyScript 使更多开发人员可以快速上手 WebAssembly,这样我们就能在一般场景中继续使用 JavaScript,而在需要大量数字运算的任务中轻松切换到 WebAssembly 了。


原文链接


https://blog.logrocket.com/the-introductory-guide-to-assemblyscript/


2019 年 12 月 04 日 11:462594
用户头像
王文婧 InfoQ编辑

发布了 126 篇内容, 共 62.2 次阅读, 收获喜欢 252 次。

关注

评论

发布
暂无评论
发现更多内容

架构师实战营[M1]-微信的业务架构和学生管理系统架构设计

LeoWang

架构实战营第一期作业

王华

架构实战营

面试题: 合并两个有序链表

木子的昼夜

架构实战营 - 模块 1- 作业

请弄脏我的身体

架构实战营

大数据分析之分析模型介绍

大数据技术指南

数据分析 4月日更

【LeetCode】合并两个有序数组Java题解

HQ数字卡

算法 LeetCode 4月日更

话说 ReadWriteLock

木子的昼夜

进程、线程、协程

无心

网络编程 操作系统

话说 内存屏障,有序性保证

木子的昼夜

面试题 : 一个单调递增的数组 随机拿出一个数 你怎么找到这个数

木子的昼夜

什么是架构?怎么来理解?

秋天

架构师 架构·

面试的信心来源于过硬的基础(iOS开发方向)

ios 面试、

团队里不能留的三种人

石云升

辞退 28天写作 职场经验 管理经验 4月日更

话说 面试题连环问

木子的昼夜

话说 线程的概念&生命周期

木子的昼夜

话说 ReentrantLock_源码

木子的昼夜

中国唯一入选 Forrester 领导者象限,阿里云 Serverless 产品能力全球第一

阿里巴巴中间件

架构实战训练营-模块一课后作业

Johnny

架构实战营

话说 线程切换&线程数设置

木子的昼夜

聪明人的训练(五)

Changing Lin

4月日更

话说 线程创建&启动&停止

木子的昼夜

话说 LockSupport

木子的昼夜

自研规则/流程引擎-ice

迟到的月亮

规则引擎

业务架构训练营第0期模块一作业

目标一个亿

话说 用户线程&守护线程&线程组&线程优先级

木子的昼夜

话说 Lock condition

木子的昼夜

话说 ReadWriteLock 第二篇

木子的昼夜

架构师实战营 [模块一]- 微信业务架构和学生管理系统架构设计

ifc177

架构实战营

面试题: String "123" 转 int类型

木子的昼夜

用 JavaScript 实现时间轴与动画 - 前端组件化

三钻

JavaScript 前端 动画 组件化 时间轴

C++ sort 排序及自定义排序

玄兴梦影

AssemblyScript如何帮助WebAssembly发挥潜力?-InfoQ