浏览器第四种语言-WebAssembly

简介: WebAssembly 是个啥? 推荐阅读指数⭐️⭐️⭐️⭐️⭐️使用 Emscripten 写一个属于你的 wasm 推荐阅读指数⭐️⭐️⭐️⭐️⭐️胶水代码 推荐阅读指数⭐️⭐️⭐️⭐️编译目标及编译流程 推荐阅读指数⭐️⭐️⭐️


先认识它,再驾驭它

大家好,我是柒八九

ChatGPT知道吧!现在最新的new Bing中已经接入了AI功能。

而能够实现上述让人欲罢不能的功能。OenAI是永远绕不开的话题。

OpenAI 是一家人工智能研究机构,他们在 2020 年推出了一款基于 WebAssembly 的 AI 模型推理引擎,名为 MicroscopeMicroscope 可以在现代浏览器中运行,提供了高效的 AI 模型推理能力。

既然,AI的模型,我们搞不定;那么WebAssembly这种更贴近前端开发者的技术,我们还是可以窥探一番的。

好了,天不早了,我们开始今天WebAssembly基础知识的探索之旅。

你能所学到的知识点

  1. WebAssembly 是个啥?  推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  2. 使用 Emscripten 写一个属于你的 wasm  推荐阅读指数⭐️⭐️⭐️⭐️⭐️
  3. 胶水代码  推荐阅读指数⭐️⭐️⭐️⭐️
  4. 编译目标及编译流程  推荐阅读指数⭐️⭐️⭐️

WebAssembly是个啥?

WebAssembly(简称Wasm)是一种可以在现代Web浏览器中运行的低级字节码

  • 它是一种可移植、大小合理和加载速度快的格式,适用于Web上的各种应用程序。

WebAssembly 是一种新的编程语言,并不是JavaScript的替代品。相反,它是一种补充,可以与现有的Web技术一起使用。WebAssembly 可以被编译成 JavaScript,也可以直接在浏览器中运行。

WebAssembly 也是新一代Web 虚拟机标准,可以让用各种语言编写的代码都能以接近原生的速度在Web中运行

  1. C/C++代码可以通过Emscripten工具链编译为wasm二进制文件,进而导入网页中供js调用
  2. Rust语言更是内置了对WebAssembly的支持

WebAssembly 诞生背景

在目前的Web应用中,JavaScript属于一家独大的地位。但是,由于JS弱类型语言,变量类型不是固定的,在使用变量前需要判断其类型,无疑增加了运算的复杂度,降低了执行效率

为了提高JS的效率,Mozila的工程师创建了Emscripten项目,尝试通过LLVM工具链将C/C++语言编写的程序转译为JS代码,并在此过程中创建了JS子集asm.js)。

asm.js仅包含可以预判变量类型的数值运算,有效地避免了JS弱类型变量语法带来的执行效率低的痛点。

asm.js显著的提升了JS效率,获得了主流浏览器厂商的支持。并且,各大厂商决定采用二进制格式来表示asm.js模块(减少模块体积,提升模块加载和解析速度),最终演化出WebAssembly技术。


Web的第四种语言

见图知意,WebAssembly已经被内置到浏览器中了。同时,.wasm可以直接运行在浏览器中。作为网页开发的第四大主力开发语言。

在浏览器控制台中,直接打印就可以看到WebAssembly构造函数。


WebAssembly解决的痛点

下面,我们来简单复现一下,V8是如何处理JS的。

  1. V8接收到要执行的JS 源代码
  • 源代码V8 来说只是一堆字符串V8 并不能直接理解这段字符串的含义
  1. V8结构化这段字符串,生成了{抽象语法树|AST},同时还会生成相关的作用域
  2. 生成字节码(介于AST机器代码的中间代码)
  • 与特定类型的机器代码无关
  1. 解释器(ignition),按照顺序解释执行字节码,并输出执行结果。

通过V8js转换为字节码然后经过解释器执行输出结果的方式执行JS,有一个弊端就是,如果在浏览器中再次打开相同的页面,当页面中的 JavaScript 文件没有被修改,再次编译之后的二进制代码也会保持不变,意味着编译这一步浪费了 CPU 资源

为了,更好的利用CPU资源,V8采用JIT(Just In Time)技术提升效率:而是混合编译执行和解释执行这两种手段

JIT引入了两个编译器

  1. 基线编译器
  • 如果一段代码变成了 warm,那么 JIT 就把它送到编译器去编译,并且把编译结果存储起来
  1. 优化编译器
  • 如果一个代码段变得 very hot监视器会把它发送到优化编译器中。生成一个更快速和高效的代码版本出来,并且存储之
  • 优化编译器最成功一个特点叫做{类型特化|Type specialization}
  • 因为JS是动态类型语言,在代码运行过程中,如果是多形态的(即调用的过程中,类型不断变化),则会为操作所调用的每一个类型组合生成一个桩。
  • 如果存在多形态的情况,无形中就会增加了JS编译执行的时间

我们可以从几个方面来描述一下,WebAssembly是如何解决现有问题的。

角度 方式
汇编角度 WebAssembly提供了一种更接近于机器码的中间表示形式,使得代码在浏览器中的执行速度更快。它允许开发者编写高性能的代码,同时保持跨平台兼容性
v8中的JIT JavaScript在浏览器中通过JIT(Just-In-Time)编译器执行,但JIT编译过程需要时间,WebAssembly的二进制格式可以更快地解码和执行。这意味着WebAssembly可以减少浏览器在解析和优化代码方面的开销,从而提高性能。
类型特化角度 JavaScript是一种动态类型语言,这意味着在运行时需要进行类型检查和转换。WebAssembly则是静态类型的,这使得它在编译和执行时可以避免这些类型检查和转换的开销。此外,静态类型还有助于提高代码的可读性和可维护性。
JVM角度 WebAssembly提供了一种独立于语言和平台的虚拟机,类似于JVM,但专为Web而设计,使得各种编程语言都可以在浏览器中高效运行。

WebAssembly 优点

角度 原因
性能 WebAssembly 代码执行速度接近原生代码,因为它是为快速解码和执行而设计的。
安全 WebAssembly 在沙箱环境中运行,保护系统资源免受恶意代码的侵害。
可移植性 WebAssembly 模块可以在任何支持的浏览器和平台上运行,无需修改。
JavaScript 互操作 WebAssembly 可以与 JavaScript 代码无缝协作,使得开发者可以在性能关键部分使用 WebAssembly,而在其他部分使用 JavaScript
语言支持 WebAssembly 支持多种编程语言,如 C、C++、Rust 等,使得开发者可以使用熟悉的语言编写高性能 Web 应用。

WebAssembly应用

WebAssembly 目前已经得到了许多公司的支持和应用,以下是一些落地项目和成就的例子:

  • Unity TechnologiesUnity 是一家游戏引擎和游戏开发工具提供商,他们在 2018 年推出了一款基于 WebAssembly 的游戏引擎,名为 "Unity 2018.2"。这款引擎可以在现代浏览器中运行,提供了与原生应用程序相同的性能和功能。
  • FastlyFastly 是一家内容传递网络(CDN)提供商,他们在 2019 年推出了一款名为 "Lucet" 的 WebAssembly 运行时。Lucet 可以在云端和边缘设备上运行 WebAssembly 代码,提供了比传统服务器更高的性能和可扩展性。
  • FigmaFigma 是一款基于 Web 的界面设计工具,他们在 2020 年推出了一款名为 "FigJam" 的新产品,其中使用了 WebAssembly 技术。FigJam 可以在浏览器中实时协作,并提供了高效的图形处理能力。
  • OpenAIOpenAI 是一家人工智能研究机构,他们在 2020 年推出了一款基于 WebAssembly 的 AI 模型推理引擎,名为 MicroscopeMicroscope 可以在现代浏览器中运行,提供了高效的 AI 模型推理能力。(最近名声大噪的-ChatGPT4你是否了解呢。神器一般的存在)

使用 Emscripten 写一个属于你的 wasm

Emscripten是用C/C++语言开发WebAssembly应用的标准工具,是WebAssembly宿主接口事实上的标准之一。

安装 Emscripten

Emscripten包含了将C/C++代码编译为WebAssembly所需的完整工具集LLVM/Node.js/Python/Java等),不依赖于任何其他的编译器环境。

可以使用emsdk命令行工具安装Emscripten

下载最新版的Python

emsdk是一组基于Python的脚本。我们可以在Python 官网下载并安装最新版的Python

$ python --version // 3.11.2
复制代码

下载emsdk

Python准备就绪后,下载emsdk工具包。

// 下载emsdk
$ git clone https://github.com/emscripten-core/emsdk.git
复制代码

安装并激活Emscripten

在控制台切换到emsdk所在目录。

针对MacOS或者Linux用户,可以按照下面的代码进行配置处理。

$ cd emsdk
复制代码

运行以下emsdk命令从GitHub获取最新工具,并将其设置为活动状态

# 获取最新版本的emsdk (第一次clone项目的时候,忽略此操作)
git pull
# 下载按照最新的SDK工具
./emsdk install latest
# 针对当前用户,将最新的SDK设置为“激活状态”
./emsdk activate latest
# 激活当前终端中的路径和其他环境变量
source ./emsdk_env.sh
复制代码

上面的命令中的输出,这里就不贴图了

对于Windows用户,按照Emscripten的方法基本一致。执行代码的区别是使用emsdk.bat代替emsdk,使用emsdk_env.bat代替source ./emsdk_env.sh

emsdk.bat update
# 下载按照最新的SDK工具
emsdk.bat install latest
# 针对当前用户,将最新的SDK设置为“激活状态”
emsdk.bat activate latest
# 激活当前终端中的路径和其他环境变量
emsdk_env.bat
复制代码

Note: 安装及激活 Emscripten只需要执行一次,然后在新建的控制台中设置一次环境变量,既可使用Emscripten核心命令emcc

emcc 全局安装

如果想要在全局范围内,使用emcc。可以使用如下步骤:

  1. vim ~/.bash_profile
  2. source 你的emsdk安装路径/emsdk_env.sh

校验安装

Emscripten安装/激活且设置环境变量后,可以通过emcc -v查看版本信息。

> emcc -v
// 以下是控制台输出日志:
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.33 (c1927f22708aa9a26a5956bab61de083e8d3e463)
clang version 17.0.0 (https://github.com/llvm/llvm-project 671eeece457f6a5da7489f7b48f7afae55327b8b)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /Users/PersonWorkSpace/WasmWorkSpace/emsdk/upstream/bin
复制代码

编码环节

又到了,我们接触新语言的环节 -- 写一个hello,world程序。

生成.wasm文件

由于我们是用Emscripten作为案例演示,所以我们用C语言来写代码

新建一个名为hello.ccC源文件。

#include <stdio.h>
int main(){
  printf("hello,world!\n");
  return 0;
}
复制代码

进入控制台,执行以下命令进行编译:

emcc hello.cc
复制代码

hello.cc所在的目录下得到两个文件

  1. a.out.wasm
  • 该文件为C源文件编译后形成的WebAssembly汇编文件
  1. a.out.js
  • Emscripten生成的胶水代码,其中包含了Emscripten的运行环境和.wasm文件的封装
  • 导入a.out.js既可自动完成.wasm文件的载入/编译/实例化、运行时初始化等工作。

我们还可以使用-o选项指定emcc的输出文件

emcc hello.cc -o hell.js
复制代码

hello.cc所在的目录下得到两个文件 分别为 hello.wasmhello.js

代码引用

与原生代码不同,C/C++代码被编译为WebAssembly后是无法直接运行的。我们需要将其导入网页,通过浏览器来执行。

在HTML中引用JS

我们在vscode中使用emmet直接搞一个最简单的html。然后引入我们刚才生成的hello.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Emscripten</title>
</head>
<body>
    <script src="./hello.js"></script>
</body>
</html>
复制代码

然后,还有一点需要注意,WebAssembly是需要通过网页发布后才可以运行。

这里,我们用node写了一个最简单的服务器。

const http = require("http"),
      fs = require("fs"),
      path = require("path"),
      url = require("url");
// 获取当前目录
let root = path.resolve();
// 创建服务器
let sever = http.createServer(function(request,response){
    let pathname = url.parse(request.url).pathname;
    let filepath = path.join(root,pathname);
    // 获取文件状态
    fs.stat(filepath,function(err,stats){
        if(err){
            // 发送404响应
            response.writeHead(404);
            response.end("404 Not Found.");
        }else{
            // 发送200响应
            response.writeHead(200);
            // response是一个writeStream对象,fs读取html后,可以用pipe方法直接写入
            fs.createReadStream(filepath).pipe(response);
        }
    });
});
sever.listen(7899);
console.log('Sever is running at http://127.0.0.1:7899/');
复制代码

这样,我们就可以通过http://127.0.0.1:7899/hello.html访问到刚才生成的hello.js了。

然后,项目的结构如下:

http://127.0.0.1:7899/hello.html的控制台,就能看到hello,world的输出结果。

在Node 环境下使用

WebAssembly程序也可以在Node.js 8+版本中运行。


在Vite中使用

如果大家对Vite熟悉的话,它是支持直接将.wasm文件引入到项目中的。

这里就直接拿来主义了哈。

利用vite-plugin-wasm插件进行引入处理

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import wasm from "vite-plugin-wasm";
export default defineConfig({
  plugins: [react(),wasm()],
})
复制代码

预编译的 .wasm 文件可以通过 ?init 来导入。默认导出一个初始化函数,返回值为所导出 wasm 实例对象的 Promise

import init from './example.wasm?init'
init().then((instance) => {
  instance.exports.test()
})
复制代码

但是呢,如果你把上面利用Emscripten生成的hello.wasm会报错。

TypeError: WebAssembly.Instance(): 
Import #0 module="wasi_unstable" error: module is not an object or function
复制代码

使用 emscripten 构建的 wasm 模块,推荐的做法是让 emscripten 生成 JS 来实现这些 API,并为你加载模块。


在网页中直接使用wasm

使用 WebAssembly 可以在网页中运行更快、更强大的应用程序。要在网页中使用 WebAssembly,需要遵循以下步骤:

  1. 编写 WebAssembly 模块,可以使用 C/C++、Rust 等语言编写。
  2. WebAssembly 模块编译为 wasm 格式。
  3. JavaScript 中加载 wasm 模块。
  4. JavaScript 中调用 wasm 模块中的函数。

下面是一个简单的例子,演示如何在网页中使用 WebAssembly

我们改造一下刚才的hello.cc

#include <stdio.h>
int add(int a, int b) {
  return a + b;
}
int main() {
  int a = 2;
  int b = 3;
  int result = add(a, b);
  printf("The sum of %d and %d is %d\\n", a, b, result);
  return 0;
}
复制代码

使用Emscripten编译器将该代码编译为WebAssembly格式。以下是一个示例命令:

emcc hello.c -o hello.wasm -s WASM=1 -s EXPORTED_FUNCTIONS="['_main', '_add']"
复制代码

该命令将_main_add函数作为可导出的函数,以便在WebAssembly模块中调用它们。然后,您可以将生成的WASM文件嵌入到HTML文件中,并使用JavaScript代码调用它们。

// 加载 wasm 模块
fetch('hello.wasm')
  .then(response => response.arrayBuffer())
  .then(bytes => WebAssembly.instantiate(bytes))
  .then(results => {
    // 调用 wasm 模块中的函数
    const { add } = results.instance.exports;
    console.log(add(1, 2)); // 输出 3
  });
复制代码

在上面的例子中,我们

  1. 首先使用 fetch 函数加载 wasm 模块,
  2. 然后使用 WebAssembly.instantiate 函数将其实例化。
  3. 最后,我们可以通过 results.instance.exports 对象访问 wasm 模块中的函数,并在 JavaScript 中调用它们。

胶水代码

Emscripten在编译时,生成了大量的JS胶水代码。

我们通过VScode打开hello.js发现,大多数的操作都围绕全局对象Module展开。该对象正是Emscripten程序运行的核心所在。

我们可以通过vscode快捷键Ctrl+K+0将所有函数折叠起来。这样方便查看顶层函数的定义。

WebAssembly汇编模块载入

WebAssembly汇编模块(即.wasm)的载入是在instantiateAsync中完成的。

上述代码就做了几件事

  1. 尝试使用 WebAssembly.instantiateStreaming()创建wasm模块的实例
  2. 如果流式创建失败,改用WebAssembly.instantiate()方法创建实例
  3. 成功实例化后返回值交由receiveInstance方法处理

receiveInstance中执行了下面的指令:

Module['asm'] = exports;
复制代码

意思就是将wasm模块实例的导出对象传给Module的子对象asm

异步加载

WebAssembly实例是通过WebAssembly.instantiateStreaming()WebAssembly.instantiate()方法创建的,而这两个方法均为异步调用,这就意味着.js加载完成时,Emscripten的运行时并未准备就绪。

就会出现在载入hello.js后,立即调用Module._main()会报错。

解决这一问题需要建立一种运行时准备就绪的通知机制。我们可以使用onRuntimeInitialized回调。

<body>
    <script>
        Module ={};
        Module.onRuntimeInitialized = function(){
            Module._main();
        }
    </script>
    <script src="./hello.js"></script>
</body>
复制代码

基本思路就是在Module初始化前,向Module中注入一个名为onRuntimeInitialized的方法,当Emscripten的运行时准备就绪时,将会回调该方法。

hello.js中的run()中调用了onRuntimeInitialized


编译目标及编译流程

Emscripten可以设定两种不同的编译目标

  1. WebAssembly
  2. asm.js

编译目标的选择

asm.js为编译目标时,C/C++代码被编译为.js文件;以WebAssembly为编译目标时,C/C++代码被编译为.wasm文件及对应的.js胶水代码文件。

二者在实际应用中主要区别在于模块加载的同步还是异步:

  • asm.js为编译目标时,由于C/C++代码被完全转换成asm.js(JS子集),因此认为模块是同步加载
  • WebAssembly为编译目标时,由于WebAssembly的实例化方法本身是异步指令,因为认为模块是异步加载

在兼容性允许的情况下,应尽量以WebAssembly为编译目标

编译流程

C/C++代码通过Clang编译为LLVM字节码,然后根据不同的目标编译为asm.jswasm


后记

分享是一种态度

参考地址

  1. emscripten.org
  2. WebAssembly
  3. 面向WebAssembly编程

全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。


相关文章
|
6月前
|
Rust JavaScript 前端开发
WebAssembly 技术:解锁浏览器的无限潜能
随着互联网的快速发展,Web 应用程序的功能需求也越来越复杂。传统的 JavaScript 语言在处理大规模数据和高性能计算方面存在一些局限性。然而,WebAssembly 技术的出现改变了这一切。本文将介绍什么是 WebAssembly,它的应用领域以及如何使用它来提升 Web 应用程序的性能和体验。
|
2月前
|
监控 安全
公司如何监控员工电脑:WebAssembly 语言的浏览器端探索
在数字化办公环境中,公司可能需要对员工电脑进行监控以确保信息安全和工作效率。WebAssembly 作为一种高性能的二进制指令格式,可在浏览器端实现复杂监控功能,如获取浏览器信息和监测网络活动,无需安装额外软件,降低了系统侵入性。然而,公司在实施监控时需遵守法律法规和道德规范,确保员工隐私得到保护,并明确告知监控目的与范围。
53 3
|
3月前
|
Rust 安全 JavaScript
Rust 和 WebAssembly 搞大事啦!代码在浏览器中运行,这波操作简直逆天!
【8月更文挑战第31天】《Rust 与 WebAssembly:将 Rust 代码运行在浏览器中》介绍了 Rust 和 WebAssembly 的强大结合。Rust 是一门安全高效的编程语言,而 WebAssembly 则是新兴的网页技术标准,两者结合使得 Rust 代码能在浏览器中运行,带来更高的性能和安全性。文章通过示例代码展示了如何将 Rust 函数编译为 WebAssembly 格式并在网页中调用,从而实现复杂高效的应用程序,同时确保了内存安全性和跨平台兼容性,为开发者提供了全新的可能性。
146 0
|
5月前
|
安全 JavaScript 前端开发
Wasmer 3.0 发布,可在浏览器外运行 WebAssembly
Wasmer 3.0 发布,可在浏览器外运行 WebAssembly
59 2
|
5月前
|
机器学习/深度学习 人工智能 前端开发
WebAssembly:浏览器中的新语言,引领Web性能革命
【6月更文挑战第12天】WebAssembly,简称Wasm,是浏览器中的新语言,旨在带来近乎原生的性能,引领Web性能革命。它具有高效、可移植、安全和多语言支持的特点,适用于游戏开发、图形处理、计算机视觉等领域。随着浏览器支持增强,Wasm将在跨平台应用、AI、机器学习、云计算和边缘计算中发挥更大作用,推动Web应用的发展。
|
Rust JavaScript 前端开发
WebAssembly:将高性能应用带入浏览器
WebAssembly(缩写为 Wasm)技术它为 Web 开发者提供了一种将高性能应用带入浏览器的途径。本文将深入探讨 WebAssembly 的概念、优势以及对 Web 开发的影响。
159 0
|
机器学习/深度学习 网络协议 数据建模
世界各国浏览器语言代码本地化对照表
世界各国浏览器语言代码本地化对照表
|
Rust JavaScript 前端开发
WebAssembly入门:构建高性能的浏览器应用
WebAssembly(简称为Wasm)是一种面向Web的二进制格式,旨在提供高性能的浏览器应用程序。它允许开发者使用多种编程语言来构建功能强大、快速运行的Web应用。本文将带你入门WebAssembly,并展示如何使用它构建高性能的浏览器应用。
11283 0
|
Web App开发 JavaScript 前端开发
让C代码在浏览器中运行——WebAssembly入门介绍
WebAssembly作为一种新兴的Web技术,相关的资料和社区还不够丰富,但其为web开发提供了一种崭新的思路和工作方式,未来是很有可能大放光彩的。 使用WebAssembly,我们可以在浏览器中运行一些高性能、低级别的编程语言,可用它将大型的C和C++代码库比如游戏、物理引擎甚至是桌面应用程序导入Web平台。
2192 0

热门文章

最新文章