阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

如何使用 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:003018

评论

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

积分商城系统软件开发方案设计初稿

西安链酷科技

积分商城

教学必备的9个教案和课件网站!建议收藏!

彭宏豪95

效率工具 培训 在线白板 办公软件 在线教学

GaussDB(for Redis)特性揭秘:大key治理

YG科技

Stepn跑鞋NFT链游系统开发跑步玩法介绍

西安链酷科技

运动挖矿

一文读懂兼顾隐私、高性能和可拓展的公链Partisia Blockchain

加密眼界

Cloud Kernel SIG 月度动态:ANCK 特性新增芯片厂商支持、新版本发布

OpenAnolis小助手

操作系统 国产操作系统 龙蜥社区 龙蜥社区SIG

钱包存币质押理财系统开发案例

西安链酷科技

钱包系统开发

一文读懂兼顾隐私、高性能和可拓展的公链Partisia Blockchain

大瞿科技

Go连接池复用踩坑

三七互娱后端技术团队

golang

NFT链游Jogger慢跑者系统开发(跑鞋模式)

西安链酷科技

NFT链游

将比特币安全性带到Persistence One

股市老人

智算创新 云峦同行!龙蜥衍生版浪潮信息 KeyarchOS 的 10 年发展历程

OpenAnolis小助手

开源 操作系统 国产操作系统 龙蜥社区

虚拟仿真云:从传统仿真到云仿真的转变与应用

3DCAT实时渲染

虚拟仿真 仿真云

WorkPlus Meet构建局域网视频会议解决方案,助力企业协同与沟通

WorkPlus

短剧视频app软件开发公司

西安链酷科技

微短剧 短剧app开发

短剧CPS分销系统程序开发(聚合CPS)

西安链酷科技

短剧app开发

《龙蜥理事说》正式上线,龙蜥携手浪潮信息共同应对 AI 时代新需求

OpenAnolis小助手

开源 操作系统 国产操作系统 龙蜥社区

GaussDB(for Redis)游戏实践:玩家下线行为上报

YG科技

RAG 修炼手册|RAG 敲响丧钟?大模型长上下文是否意味着向量检索不再重要

Zilliz

大模型 Zilliz 向量数据库 rag

ai智能写作网站免费!5款工具轻松生成高质量内容!

彭宏豪95

人工智能 写作 在线白板 AIGC AI工具

《计算机网络: 自顶向下方法(原书第7版)》PDF

程序员李木子

提升龙蜥内核测试能力!探究持续性模糊测试优化实践

OpenAnolis小助手

操作系统 国产操作系统 龙蜥社区 Anolis OS

NVIDIA AI 新网络助力龙蜥提升网络通信速率

OpenAnolis小助手

AI 操作系统 国产操作系统 龙蜥社区

体验 AIGC 魅力!龙蜥社区邀请您从零开始部署运行 GPT-2 大语言模型

OpenAnolis小助手

操作系统 国产操作系统 龙蜥社区 人人都可以参与开源

英特尔锐炫最新驱动首日支持国产武侠大作《射雕》,开启流畅武侠之旅!

E科讯

Partisia Blockchain:如何做到兼顾隐私、高性能和可拓展?

西柚子

5大支撑能力,6大系统优势!解读龙蜥场景化镜像平台技术

OpenAnolis小助手

AI 操作系统 国产操作系统 龙蜥社区

在线教学用什么软件?这款可视化白板工具值得推荐!

彭宏豪95

培训 在线白板 办公软件 在线协作 在线教学

WorkPlus最佳的内网通讯软件解决方案,助力企业构建高效沟通环境

WorkPlus

NewFi质押生息系统Dapp开发|BSC链智能合约开发技术

西安链酷科技

DAPP智能合约交易系统开发

C++ 字符串完全指南:学习基础知识到掌握高级应用技巧

小万哥

程序人生 编程语言 软件工程 C/C++ 后端开发

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