AssemblyScript 入门指南

2020-11-23 09:26:08

AssemblyScript 是一个把 TypeScript 转换到 WebAssembly 的编译器。由微软开发的 TypeScript 将类型添加到了 JavaScript 中。它已经变得相当受欢迎,即使对于不熟悉它的人,AssemblyScript 只允许 TypeScript 的有限功能子集,因此不需要花太多时间就可以上手

参考链接 AssemblyScript 入门指南   官方文档  https://www.assemblyscript.org/quick-start.html


可以直接参考官方文档,但是如果你的逻辑不是很负责,我还是推荐使用asm.js 编译来编译,asm.js编译,既能编译成asm.js,也能编译成wasm的文件,可以选择。这样的话,我觉得没有必要再使用AssemblyScript。


具体asm.js编译,请看前面的文章

WebAssembly(Wasm)是 Web 浏览器中相对较新的功能,但它地扩展了把 Web 作为服务应用平台的功能潜力。

对于 Web 开发人员来说,学习使用 WebAssembly 可能会有一个艰难的过程,但是 AssemblyScript 提供了一种解决方法。首先让我们看一下为什么 WebAssembly 是一项很有前途的技术,然后再看怎样 AssemblyScript 挖掘潜力。

WebAssembly

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

它是根据所有主流浏览器(Chrome,Firefox,Safari 和 Edge)所代表的意见开发的,他们达成了设计共识,这些浏览器现在都支持 WebAssembly。

WebAssembly 以二进制格式交付,这意味着与 JavaScript 相比,WebAssembly 在大小和加载时间上都具有优势。但是它也有易于理解的文本表示形式

当 WebAssembly 首次发布时,一些开发人员认为它有可能最终取代 JavaScript 作为 Web 的主要语言。但是最好把 WebAssembly 看作是与现有 Web 平台良好集成的新工具,这是它的高级目标

WebAssembly 并没有取代 JavaScript 现有的用例,而是吸引了更多人,因为它引入了新的用例。 目前 WebAssembly 还不能直接访问 DOM,大多数网站都希望使用 JavaScript,经过多年的优化,JavaScript 已经相当快了。以下 WebAssembly 可能的使用案例列表的示例:

  • 游戏

  • 科学的可视化和模拟

  • CAD应用

  • 图像/视频编辑

这些应用共同特点是,它们通常会被看作是桌面应用。通过为 CPU 密集型任务提供接近本机的性能,WebAssembly 使得将这些程序迁移至 Web 成为可行。

现有网站也可以从 WebAssembly 中受益。 Figma(https://www.figma.com/) 提供了一个真实的例子,它通过使用 WebAssembly 大大缩短了其加载时间。如果网站使用进行大量计算的代码,则可以将其替换为 WebAssembly 以提高性能。

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

但是也许你并不想为了使用 WebAssembly 而学习或使用其中某种语言。这就是 AssemblyScript 存在的意义。

AssemblyScript

AssemblyScript 是一个把 TypeScript 转换到 WebAssembly 的编译器。由微软开发的 TypeScript 将类型添加到了 JavaScript 中。它已经变得相当受欢迎,即使对于不熟悉它的人,AssemblyScript 只允许 TypeScript 的有限功能子集,因此不需要花太多时间就可以上手。。

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

试用

让我们编写第一个 AssemblyScript 模块(以下所有代码均可在 GitHub 上找到)。我们需要 Node.js 的最低版本为 8 才能得到 WebAssembly 的支持

转到一个空目录,创建一个 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
});

它使我们能够像使用普通的 JavaScript 模块一样轻松地 require WebAssembly 模块。

assembly 目录中包含我们的 AssemblyScript 源代码。生成的示例是一个简单的加法函数。

export function add(a: i32, b: i32): i32 {  return a + b;
}

函数签名就像在 TypeScript 中那样,它之所以使用 i32 的原因是 AssemblyScript 使用了 WebAssembly 的特定整数和浮点类型,而不是 TypeScript 的通用 number 类型

让我们来构建示例。

npm run asbuild

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

optimized.wasm
optimized.wasm.map
optimized.wat
untouched.wasm
untouched.wasm.map
untouched.wat

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

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

$ node
Welcome 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

在 package.json 中添加一个 asbuild:watch 脚本。包含 -i flag,即可在运行命令后立即运行初始构建。

{  "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

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.

请注意,这个测试是一个 microbenchmark,我们应该谨慎阅读。

对于一些更多的 AssemblyScript 基准测试,我建议你查看 WasmBoy 基准测试波动方程式基准测试

加载模块

接下来,在网站中使用我们的模块。

先创建 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 函数以流的方式编译和实例化。请注意,如果 assertion 失败的话,我们需要提供 abort 函数

(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 type 的 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。


  • 2020-12-06 20:38:30

    intellij idea远程开发remote

    开发时一般的平台都是windows,但windows对开发极其不友好,一般都会在本地开启虚拟机,安装上linux环境进行项目的部署测试。下面介绍一种windows主机与linux虚拟机代码同步的方法。这个工具适用于jerbrains公司旗下的很多产品,比如idea、webstrom、phpstrom等。但是要注意你安装的IDE必须是专业版的,社区版的IDE是没有这个代码同步功能的哦!

  • 2020-12-07 05:46:56

    npm设置和取消代理的方法

    有时候是设置了全局代理对npm并不生效,不如直接给npm设置代理,至少在mac电脑我是有这种感觉的。

  • 2020-12-07 15:04:03

    node开发邮件系统总结

    因为multipart这种形式比较复杂,因此要利用boundary分割符,将邮件体分割成不同段来进行解析,boundary分为父段和子段,父段一般出现0次或1次,出现在末尾,每个子段中也有content-type和boundary,需要在进行解析,如果遇到i,iii里面的情况可直接解析,如果遇到ii中的情况,再按ii中的步骤进行解析

  • 2020-12-07 15:17:45

    email-templates + mjml 发送邮件

    mjml 是一个很不错的响应式邮件html 内容标签库,email-templates 是一个灵活强大的邮件发送框架,两者集成起来我们 可以设计灵活强大的邮件发送系统,以下是一个简单的集成使用,实际使用还有好多地方需要完善

  • 2020-12-07 15:19:00

    响应式邮件的编写插件介绍mjml

    以前做项目碰到发邮件的需求,邮件模板的编辑就是一件头疼的事。因为虽说邮件是支持 HTML 的,但是确是 HTML 子集程度的支持,所以存在必须通过 <table> 排版的恶心之处,还有很多兼容性的坑。本质上是各家邮件商的标准有差异吧。

  • 2020-12-07 16:14:22

    nodejs队列实现amqplib,rabbitmq

    其中StartConsumer 会在项目启动时启动,在整个生命周期中一直保持监听状态,在程序结束时mq的链接关闭。需要注意的是 noAck 这个参数,当为false是表示消息出队后不会自动删除,如果设置成true,则无论消息处理成功与否此消息会被删除。注意到在消息不成功是,调用了ch.nack(msg)),此方法是将消息重新入队。

  • 2020-12-07 16:15:46

    RabbitMQ详解

    当前市面上mq的产品很多,比如RabbitMQ、Kafka、ActiveMQ、ZeroMQ和阿里巴巴捐献给Apache的RocketMQ。甚至连redis这种NoSQL都支持MQ的功能。 ActiveMQ ActiveMQ是apache出品,最流行的,能力强劲的开源消息总线,并且它一个完全支持JMS规范的消息中间件。其丰富的API、多种集群构建模式使得它成为业界老牌消息中间件,在中小型企业中应用广泛。

  • 2020-12-07 16:17:53

    nodejs用redis实现队列操作

    其实nodejs实现队列的方式又很多中,也有很多开源的插件和队列数据库可以使用,但是呢,如果我们一个简单的项目,完全可以使用redis来实现队列, 这样再不增加技术难度的同事,我们也就可以完美的实现一个队列