NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

如何使用 WebAssembly 和 JS 构建高性能应用程序

  • 2020-10-27
  • 本文字数:4744 字

    阅读完需:约 16 分钟

如何使用 WebAssembly 和 JS 构建高性能应用程序

本文最初发布于 Medium 网站,经原作者授权由 InfoQ 中文站翻译并分享。


自计算机发明以来,原生应用程序的性能有了巨大的提升。相比之下,由于 JavaScript 最初并不是为提高速度而构建的,因此 Web 应用程序的运行速度曾经相当缓慢。但是,由于浏览器之间的激烈竞争以及 JavaScript 引擎(例如 V8)的迅速发展,JavaScript 在机器上的运行速度也变得非常快了。但是它仍然无法在速度上击败原生应用程序。这主要是由于 JavaScript 代码必须经过多个流程才能生成机器代码。



JS 引擎花费的平均时间


随着 WebAssembly 的引入,现代 Web 为我们所知的一切都有望迎来变革。这项技术快如闪电。在这篇文章文章中,我们就来看一下什么是 WebAssembly,以及如何将它与 JavaScript 集成以构建高性能应用程序。

什么是 WebAssembly?

在深入了解 WebAssembly 之前,我们先来看一下什么是 Assembly。


汇编(Assembly)是一种底层编程语言,与 CPU 架构的机器级指令有着非常紧密的联系。换句话说,它离机器可理解的代码(称为机器代码)只差一个转换过程。这种转换过程称为汇编


顾名思义,WebAssembly 可以理解为 Web 的汇编。它是一种类似于汇编语言的底层语言,有着紧凑的二进制格式,使你能够以接近原生的速度运行 Web 应用程序。它还为 C、C++和 Rust 等语言提供了编译目标,从而使客户端应用程序能够以接近原生的性能运行在 Web 上。


此外,WebAssembly 被设计为与 JavaScript 并存,而不是替代后者。使用 WebAssembly JavaScript API,你可以在两种语言之间来回交换代码,而不会出现任何问题。这样,你就可以获得同时具备 WebAssembly 的功能和性能,以及 JavaScript 的多功能和适应性的应用程序。这打开了一个 Web 应用程序的全新世界,我们可以在 Web 上运行很多原本不准备用于 Web 的代码和功能。

它带来了什么变化

Lin Clark 预测,2017 年推出的 WebAssembly 可能会让 Web 开发产业迎来全新的拐点。上一个拐点来自现代浏览器中引入的 JIT 编译,其使 JavaScript 的速度提高了近 10 倍。



JavaScript 性能


如果对比 WebAssembly 与 JavaScript 的编译过程,你会注意到前者剥离了几个步骤,剩下的都被缩减了。下图是两种语言编译过程的直观对比。



WebAssembly 与传统 Web 应用程序编译过程的近似对比


仔细对比两者,你会注意到 WebAssembly 中的重优化部分已被完全剥离。这主要是因为:编译器无需对 WebAssembly 代码做任何假设,因为数据类型之类的东西在代码中是显式展现的。


但 JavaScript 不是这样,因为 JIT 应该为运行代码做出假设,如果假设失败,则应重优化代码。

如何获取 WebAssembly 代码

WebAssembly 是一项伟大的技术,但是你该如何使用它的力量呢?


你有几种方法可用。


  • 从头开始编写 WebAssembly 代码——除非你非常了解它的基础知识,否则完全不建议这样做。

  • 从 C 编译为 WebAssembly

  • 从 C++编译为 WebAssembly

  • 从 Rust 编译为 WebAssembly

  • 使用 AssemblyScript 将 Typescript 的一个严格变体编译为 WebAssembly。对于不熟悉 C/C++或 Rust 的 Web 开发人员来说,这是一个不错的选项。

  • Wasm 还支持更多语言选项,后文会提到。


此外,还有 Emscripten 和 WebAssembly Studio 之类的工具可以帮助你完成上述过程。

JavaScript 的 WebAssembly API

为了充分利用 WebAssembly 的功能,我们必须将其与 JavaScript 代码集成在一起。这可以在 JavaScript WebAssembly API 的帮助下完成。

模块编译和实例化

WebAssembly 代码位于.wasm 文件中。该文件应编译为针对底层机器的机器码。你可以使用 WebAssembly.compile 方法来编译 WebAssembly 模块。收到已编译的模块后,可以使用 WebAssembly.instantiate 方法实例化已编译的模块。另外,你也可以将获取.wasm 文件获得的数组缓存传递到 WebAssembly.instantiate 方法中。这也可以,因为实例化方法有两个重载。


let exports;fetch('sample.wasm').then(response =>  response.arrayBuffer();).then(bytes =>  WebAssembly.instantiate(bytes);).then(results => {  exports = results.instance.exports;});
复制代码


上述方法的缺点之一是这些方法不能直接访问字节码,因此在编译/实例化 wasm 模块之前,需要采取额外的步骤将响应转换为 ArrayBuffer。


相比之下,我们可以使用 WebAssembly.compileStreaming/WebAssembly.instantiateStreaming 方法来实现上述功能,其优点是可以直接访问字节码,而无需将响应转换为 ArrayBuffer。


let exports;WebAssembly.instantiateStreaming(fetch('sample.wasm')).then(obj => {  exports = obj.instance.exports;})
复制代码


应注意,WebAssembly.instantiate 和 WebAssembly.instantiateStreaming 会返回实例以及已编译的模块,这些实例可用于快速启动模块的实例。


let exports;let compiledModule;WebAssembly.instantiateStreaming(fetch('sample.wasm')).then(obj => {  exports = obj.instance.exports;  //access compiled module  compiledModule = obj.module;})
复制代码

导入对象

实例化 WebAssembly 模块实例时,可以选择传递一个导入对象,该对象将包含要导入到新创建的模块实例中的值。它们可以是 4 种类型。


  • 全局变量值

  • 函数

  • memory

  • table


导入对象可以视为提供给模块实例以帮助其完成任务的工具。如果未提供导入对象,则编译器将分配默认值。

全局变量

WebAssembly 允许你创建可从 JavaScript 和 WebAssembly 模块访问的全局变量实例。你可以导入/导出这些变量,并在一个或多个 WebAssembly 模块实例中使用它们。


你可以使用 WebAssembly.Global()构造器创建一个全局实例。


const global = new WebAssembly.Global({    value: 'i64',    mutable: true}, 20);
复制代码


全局构造器接收两个参数。


  • 一个对象,包含描述全局变量的数据类型和可变性的属性。允许的数据类型为 i32、i64、f32 或 f64

  • 实际变量的初始值。此值应为参数 1 中提到的类型。例如,如果你声明类型为 i32,则变量应为 32 位整数。同样,如果你声明类型为 f64,则变量应为 64 位浮点数。


const global = new WebAssembly.Global({    value: 'i64',    mutable: true}, 20);let importObject = {    js: {        global    }};WebAssembly.instantiateStreaming(fetch('global.wasm'), importObject)
复制代码


全局实例应传递到 importObject 上,以便在 WebAssembly 模块实例中访问它。

Memory

在实例化时,WebAssembly 模块将需要分配一个 memory 对象。该 memory 对象应与 importObject 一起传递。如果没能这样做,则 JIT 编译器将使用默认值自动创建一个 memory 对象并将其附加到实例。


附加到模块实例的 memory 对象只是一个 ArrayBuffer。只需使用索引值,即可轻松访问 memory。此外,由于它是简单的 ArrayBuffer,因此可以简单地在 JavaScript 和 WebAssembly 之间传递和共享值。

Table

WebAssembly Table 是一个可调整大小的数组,位于 WebAssembly 的 memory 之外。该 Table 的值都是函数引用。尽管这听起来很像 WebAssembly memory,但它们是不同的,主要区别在于 Memory 数组是原始字节,而 Table 数组是引用。


引入 Table 主要是为了提高安全性。


你可以使用 set()、grow()和 get()方法来操作 Table。

演示

在这个演示中,我将使用 WebAssembly Studio 应用程序将一个 C 文件编译为.wasm。你可以在这里查看演示。


我创建了一个函数来计算 wasm 文件中一个数字的幂。我将必要的值传递给函数,并在 JavaScript 中接收输出。


同样,我在 wasm 中进行了一些字符串操作。需要注意 wasm 没有字符串类型,因此它用的是 ASCII 值。返回到 JavaScript 的值将指向存储输出的 memory 位置。由于 memory 对象是 ArrayBuffer,因此我要进行迭代,直到收到字符串中的所有字符为止。

JavaScript 文件

let exports;let buffer;(async() => {  let response = await fetch('../out/main.wasm');  let results = await WebAssembly.instantiate(await response.arrayBuffer());  //or  // let results = await WebAssembly.instantiateStreaming(fetch('../out/main.wasm'));  let instance = results.instance;  exports = instance.exports;  buffer = new Uint8Array(exports.memory.buffer);  findPower(5,3);    printHelloWorld();  })();const findPower = (base = 0, power = 0) => {  console.log(exports.power(base,power));}const printHelloWorld = () => {  let pointer = exports.helloWorld();  let str = "";  for(let i = pointer;buffer[i];i++){    str += String.fromCharCode(buffer[i]);  }  console.log(str);}
复制代码

C 文件

#define WASM_EXPORT __attribute__((visibility("default")))#include <math.h>
WASM_EXPORTdouble power(double number,double power_value) { return pow(number,power_value);}WASM_EXPORTchar* helloWorld(){ return "hello world";}
复制代码

用例

WebAssembly 的诞生带来了很多全新的机遇。


  • 能够在 Web 环境中使用以 C/C++等语言编写的现有库/代码。


例如,如果你无法找到实现某些功能的 JavaScript 库,则必须从头开始编写并实现自己的库。但如果你可以找到实现相同功能,但使用不同语言编写的库,则可以使用 WebAssembly 的能力在 Web 应用程序中运行它。这是一项重大突破,因为从开发人员的角度来看,它将节省大量时间。


Squoosh应用使用 WebAssembly 来实现其 QR 和图像检测功能。这样一来,即使在较旧的浏览器上,它们也能以接近原生的速度支持这些功能。此外,eBay 将其原生应用程序的 C++库编译为 WebAssembly,从而在 Web 应用程序中实现了条形码扫描功能。


  • 无需修改代码,就能在 Web 上运行用 C、C++、Rust 等语言编写的完全原生应用程序,性能表现也接近原生水平。


诸如 AutoCAD、QT 甚至谷歌地球之类的应用程序都可以在几乎不修改代码库的情况下以接近原生的性能运行在 Web 上,多亏了 Web Assembly 的强大力量。


  • 即使你找到了用 JavaScript 编写的类似库,也可以使用以 C、C++或 Rust 等语言编写的库,因为 WebAssembly 代码速度飞快,并能提供更好的质量。


谷歌团队将来自 C 和 C++等不同语言的编码器编译到了他们的 Squoosh 应用中,并替换掉了常规的编解码器(如 JPEG -> MozJPEG)。这些替代品缩小了文件体积,而不会牺牲图像的视觉质量。

支持的语言

WebAssembly 支持不仅限于 C、C++和 Rust。许多开发人员也在努力纳入对其他语言的支持。以下是当前支持的语言列表。


  • C/C++

  • Rust

  • AssemblyScript(类似 TypeScript 的语法)

  • C#

  • F#

  • Go

  • Kotlin

  • Swift

  • D

  • Pascal

  • Zig

批评

WebAssembly 允许在已编译的二进制文件中执行,因此带来了许多安全性质疑。甚至无需什么 trace 也可以利用这些漏洞,而且它们确实很难发现。尽管 WebAssembly 也有自己的安全特性,但我个人认为这方面仍需进一步改进。因为引入了那么多新功能,防病毒工具和 URL 过滤之类的传统防护技术根本无法提供全面保护。这意味着常规浏览器在未来会更加脆弱。


关于这些问题的更多信息可以参考以下文章。


结论

有人猜测 WebAssembly 将取代 JavaScript,但我必须说这是完全错误的。创建 WebAssembly 是为了与 JavaScript 共存,而不是替代它。此外,调试 JavaScript 比 WebAssembly 容易一些,并且在 Wasm 中没有 JavaScript 的那种自由气氛。


大家都对它寄予厚望,可以肯定地说 WebAssembly 能够为一系列应用铺平道路,惊艳世人。


“没人能肯定地说这些性能改进可以催生怎样的应用。但以史为鉴,我们可以对此满怀期待” ——Lin Clark


原文链接:


https://blog.bitsrc.io/a-complete-introduction-to-webassembly-and-its-javascript-api-3474a9845206


2020-10-27 08:003030

评论

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

如何在 CentOS 8 服务器上安装 ISP Config 3.2?

Ethereal

服务器 ISP CentOS 8

如何在 Debian 11 上使用 Nginx 安装 HTTP Git 服务器

Ethereal

nginx git HTTP Debian 11

吹水的6大坏习惯

搬砖的周狮傅

沟通艺术

架构实战营 4 期第二模块作业

jialuooooo

架构实战营

文本编辑器GNU Nano 6.0 发布!

Ethereal

Nano

HTTP超时处理

JavaEdge

12月日更

16.  《重学JAVA》--1.8新日期类

杨鹏Geek

Java 25 周年 28天写作 12月日更

面向WEB开发人员的Docker(七):使用 Docker 开发Node应用程序

devpoint

node.js Docker 12月日更

【LeetCode】换酒问题Java题解

Albert

算法 LeetCode 12月日更

走进Java接口测试之简单解决写接口脏数据问题

zuozewei

Java 自动化测试 测试开发 接口自动化 12月日更

TCP报文发送的那些事

程序员历小冰

网络 TCP/IP 28天写作 12月日更

分布式锁及其实现

xcbeyond

分布式锁 28天写作 12月日更

反射

Nydia

演绎法、归纳法、辩证法

mtfelix

28天写作

不太推荐 《沟通的方法》(17/28)

赵新龙

28天写作

细节的问题

将军-技术演讲力教练

[Pulsar] Producer 流控

Zike Yang

Apache Pulsar 12月日更

性能工具之linux三剑客awk、grep、sed详解

zuozewei

Linux 性能测试 12月日更

Dubbo框架学习笔记五

风翱

dubbo 12月日更

Turbine

李子捌

微服务 28天写作 12月日更

详解PON基础知识:OLT、ONU、ONT和ODN,建议收藏

Ethereal

pon 光纤 弱电工程师

.NET 6新东西--高性能日志

喵叔

28天写作 12月日更

抬头看天

xujiangniao

知识梳理

读《思辨与立场》-05批判性思维发展的四个层级

wood

28天写作 批判性思维 思辨与立场

保护自己电脑绝对不做黑客肉鸡

喀拉峻

网络安全 安全

Go+ URL 解析教程(5.6)

liuzhen007

28天写作 12月日更

47 K8S之 Ingress资源

穿过生命散发芬芳

k8s 28天写作 12月日更

存量经营

张老蔫

模块二作业

whoami

「架构实战营」

电商秒杀系统

胡颖

架构实战营

学生管理系统架构文档

drizzle

「架构实战营」

如何使用 WebAssembly 和 JS 构建高性能应用程序_语言 & 开发_Mahdhi Rezvi_InfoQ精选文章