Rust wasm 的探索与实践

image.png

前言

Rust 和 WebAssembly 是当前非常热门的技术,它们都具有非常强大的性能和安全性。Rust 是一种系统级编程语言,具有内存安全、并发性和高效性等特点。WebAssembly 是一种新型的低级字节码,可以在浏览器中运行高性能的应用程序。Rust 与 WebAssembly 的结合可以让我们在 Web 上构建出更高效、更安全的应用程序。

本篇文章将介绍 Rust 和 WebAssembly 的基础知识,包括如何使用 Rust 编写 WebAssembly 模块,如何在 Rust 和 JavaScript 之间进行互操作,以及如何将 Rust wasm 发布到 npm 上。此外,我们还将介绍一些实际的 Rust wasm 项目案例,并对 Rust wasm 的优势和未来展望进行讨论。

Rust 语言简介

Rust 是一种由 Mozilla 开发的系统级编程语言,它结合了 C++ 和其他现代编程语言的优点。Rust 具有内存安全、并发性和高效性等特点,可以用于构建高性能的系统级应用程序。Rust 的设计目标是为了提供一个安全、并发和高效的编程语言。Rust 的语法类似于 C 和 C++,但是 Rust 有一些独特的特性,使其比传统的系统编程语言更加安全和可靠。

以下是 Rust 的一些主要特性:

  1. 内存安全:Rust 的内存安全特性可以防止一些常见的内存错误,例如空指针、野指针和数据竞争等问题。Rust 的所有权系统确保内存管理的正确性。

  2. 零成本抽象:Rust 支持高级抽象,例如泛型和 trait,但是这些抽象在编译时会被完全展开,从而避免了运行时开销。

  3. 并发安全:Rust 支持线程安全和并发编程。Rust 的所有权系统可以保证多线程程序的正确性。

  4. 高性能:Rust 的编译器可以生成高效的本机代码,使其在性能方面与 C 和 C++ 相当。

  5. 开放源代码:Rust 是一个开放源代码项目,任何人都可以参与开发和改进。

Rust 的应用领域包括操作系统、网络服务器、游戏引擎、嵌入式设备等。Rust 的生态系统非常丰富,拥有许多优秀的库和框架,可以用于各种应用场景。

Rust 的一大特点是它的所有权系统。所有权系统是 Rust 的一项内存管理功能,它确保内存分配和回收的正确性。在 Rust 中,每个值都有一个所有者,当所有者离开作用域时,值就会被释放。这种内存管理方式可以防止空指针和内存泄漏等问题。

WebAssembly 简介

image.png

WebAssembly或称wasm是一个低级编程语言。WebAssembly是便携式的抽象语法树,被设计来提供比JavaScript更快速的编译及执行。WebAssembly将让开发者能运用自己熟悉的编程语言(最初以C/C++作为实现目标)编译,再藉虚拟机引擎在浏览器内执行。WebAssembly的开发团队分别来自Mozilla、Google、Microsoft、Apple,代表着四大网络浏览器Firefox、Chrome、Microsoft Edge、Safari。2017年11月,以上四个浏览器都开始实验性的支持WebAssembly。在 2019 年 12 月 5 日,W3C制定《WebAssembly 核心规范 》,WebAssembly 正式被认证为 Web 的标准之一

以下是 WebAssembly 的一些主要特点:

  • 高效且快速:Wasm 堆栈机器被设计为使用高效的二进制格式进行编码,以实现快速的加载和执行。WebAssembly 旨在利用广泛平台上可用的通用硬件功能,在本机速度下执行。

  • 安全:WebAssembly 提供了内存安全的沙盒执行环境,可以在现有的 JavaScript 虚拟机中实现。当嵌入到 Web 中时,WebAssembly 将遵循浏览器的同源和权限安全策略,确保安全性。

  • 开放且可调试:WebAssembly 旨在以文本格式进行更好的打印,以便用于调试、测试、实验、优化、学习、教学和手动编写程序。在网络上查看 Wasm 模块的源代码时将使用文本格式,使得开发人员可以更轻松地调试和优化代码。

  • 开放式 web 平台的一部分:WebAssembly 旨在保持 web 的无版本、经过功能测试和向后兼容的特性。WebAssembly 模块可以调用和被 JavaScript 上下文调用,并且可以通过可从 JavaScript 访问的相同 Web API 访问浏览器功能。此外,WebAssembly 还支持非网络嵌入,为更广泛的应用场景提供了支持。

WebAssembly 与 JavaScript 不同,它是一种真正的二进制格式,可以直接在浏览器中运行,而不需要像 JavaScript 那样先被解释成字节码。这意味着 WebAssembly 的性能比 JavaScript 更好,并且可以被用于更高效的计算密集型任务。

Rust 与 WebAssembly

Rust 与 WebAssembly 的结合可以让我们在 Web 上构建出更高效、更安全的应用程序。Rust 可以编译成 WebAssembly 模块,这些模块可以在浏览器中运行,提供更快、更安全的性能。

安装 Rust 和 WebAssembly 工具

wasm-pack : https://rustwasm.github.io/wasm-pack/installer/
wasm-pack 是一个用于构建、测试和发布 Rust 和 WebAssembly 应用程序的工具,可以让我们快速、简单地将 Rust 代码编译为 WebAssembly 模块。

1
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

Rust 编写 WebAssembly 代码

  1. 创建一个 Rust 应用程序,并将其编写为库(library)。

    1
    cargo new rust-wasm
  2. 在应用程序的根目录下创建一个名为 Cargo.toml 的文件,并添加以下内容:

    1
    2
    3
    4
    5
    6
    7
    #这个配置告诉 Rust 的包管理器 Cargo 将应用程序编译为动态链接库(dylib),这是 WebAssembly 模块所需的格式。
    [lib]
    crate-type = ["cdylib"]

    [dependencies]
    #用于将 Rust 代码编译为 WebAssembly 模块的库。
    wasm-bindgen = "0.2"
  3. 在应用程序的根目录下创建一个名为 src/lib.rs 的文件,并在其中编写 Rust 代码。你可以使用 #[wasm_bindgen] 注解将 Rust 函数导出为 JavaScript 函数

    1
    2
    3
    4
    5
    6
    use wasm_bindgen::prelude::*;

    #[wasm_bindgen]
    pub fn add(a: i32, b: i32) -> i32 {
    a + b
    }

Rust 编译成 WebAssembly

rust编译成wasm的方式有两种,一种是通过 wasm-pack,另一种通过 cargo build –target wasm32-unknown-unknown

  1. 通过 wasm-pack 进行构造wasm

    1
    wasm-pack build --target web

    生成目标文件

    1
    2
    3
    4
    5
    6
    7
    pkg
    ├── README.md
    ├── package.json
    ├── rust_wasm.d.ts
    ├── rust_wasm.js
    ├── rust_wasm_bg.wasm
    └── rust_wasm_bg.wasm.d.ts

    wasm-pack 生成构建后的包相比 cargo 直接构建生成文件的会更丰富,能够方便我们快速的调用wasm

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <script type="module">
    import init, {calc_fib} from "./pkg/rust_wasm.js";
    init().then(() => {
    // greet("WebAssembly");
    // 开始计时
    const st = performance.now();
    const ret = calc_fib(40)
    // 结束计时
    const et = performance.now();
    // 计算执行时间
    const edt = et - st;
    console.log(`rust-wasm 代码执行时间为 ${edt} 毫秒 ${ret}`);

    });

    </script>
  2. 通过 cargo 指定构建目标为wasm32-unknown-unknown 进行构建,它会将 Rust 代码编译成 WebAssembly 模块。我们可以使用 cargo 工具来编译 Rust 代码,命令如下:

    1
    cargo build --target wasm32-unknown-unknown

这个命令会将 Rust 代码编译成 WebAssembly 模块,并生成一个 .wasm 文件。我们可以在js中进行导入使用

Rust wasm 运行原理

a.jpeg
rust编译成wasm主要是通过rust wasm的工具链来实现

JavaScript运行原理

JavaScript引擎是一种实现JavaScript语言的解释器或编译器,它负责解释和执行JavaScript代码,实现网页的动态交互和功能。JavaScript引擎的核心概念和技术:

  1. 解释器和编译器:JavaScript引擎通常包含解释器和编译器两个部分。解释器可以快速解释和执行JavaScript代码,但是性能较低;编译器可以将JavaScript代码编译为机器码,实现更高的性能,但是需要额外的编译时间和空间。
  2. JIT编译:JavaScript引擎通常采用JIT(Just-In-Time)编译技术,将JavaScript代码编译为本地机器码,并缓存编译结果,以提高代码执行的性能。
  3. 内存管理:JavaScript引擎需要负责管理内存的分配和释放,避免内存泄漏和内存溢出等问题。
  4. 优化技术:JavaScript引擎通常采用多种优化技术,例如内联缓存、代码消除、循环展开等,以提高代码的执行效率和响应速度。
  5. 跨平台支持:JavaScript引擎需要支持多种平台和操作系统,例如浏览器、移动设备等。

常见的JavaScript引擎包括V8、SpiderMonkey、JavaScriptCore等。其中,V8是Google开发的JavaScript引擎,在Chrome浏览器中得到广泛应用,具有高性能和低内存占用的特点。JavaScript引擎是实现JavaScript语言的核心技术之一,它的性能和功能对于网页的交互和用户体验至关重要。

V8引擎的结构可以分为两个主要部分:解析器和执行引擎。

解析器

解析器的主要任务是将JavaScript代码转换成抽象语法树(AST)。解析器包括以下组件:

  • 词法分析器(tokenizer):将代码分解成单个的标记(tokens),例如变量名、操作符和关键字。
  • 语法分析器(parser):将标记序列转换成抽象语法树(AST)。
  • 解释器(interpreter):执行AST并生成字节码,字节码是一种介于AST和机器码之间的中间码。

执行引擎

执行引擎的主要任务是执行AST并将其转换成机器码。执行引擎包括以下组件:

  • 编译器(compiler):将字节码转换成机器码。
  • 垃圾回收器(garbage collector):自动管理内存,回收不再使用的内存。
  • 最优化编译器(optimizer):对代码进行优化,以提高执行性能。

V8引擎使用了即时编译(JIT)的技术,它可以动态地将解释器生成的字节码转换成机器码,并且在代码执行时进行优化,以提高代码的执行速度

代码输入:JavaScript引擎接收JavaScript代码作为输入。

词法分析:JavaScript引擎将代码分解为一系列的token,每个token表示一个词法单元,例如关键字、运算符、变量名等。

语法分析:JavaScript引擎将token流转换为抽象语法树(AST),表示代码的语法结构和逻辑。

生成字节码:JavaScript引擎将AST转换为字节码,这是一种中间形式的代码表示,用于优化和执行。

JIT编译器:JavaScript引擎使用JIT编译器将字节码编译为本地机器码,以提高代码的执行效率和响应速度。

生成机器码:JavaScript引擎将编译后的机器码存储在内存中,用于后续的代码执行。

代码执行:JavaScript引擎执行机器码,实现代码的动态交互和功能。

WebAssmbly运行原理

WebAssembly 之所以比 JavaScript 更高效,是因为它的设计和实现可以更好地利用计算机硬件的特性。

WebAssembly 是一种低级字节码格式,它的指令集是基于栈的,可以直接映射到底层硬件的指令集,从而实现更好的性能。与之相比,JavaScript 是一种高级语言,其代码需要经过解释器的解释和执行,还需要进行垃圾回收、动态类型检查等操作,这些都会带来一定的性能开销。

WebAssembly 还提供了一些优化的特性,如本地变量、线性内存等,可以使得代码的性能更加高效。WebAssembly 的线性内存模型允许程序直接访问内存,而不需要通过 JavaScript 中的 ArrayBuffer 对象等中间层,从而避免了不必要的拷贝操作,提高了内存访问的效率。

WebAssembly 还支持多线程,并提供了原子操作和共享内存等机制,可以更好地利用现代计算机的多核心处理能力,实现并行计算,进一步提高程序的性能。相比之下,JavaScript 的架构主要是基于事件驱动的,通过事件驱动机制实现异步编程,避免了阻塞主线程,提高了程序的响应性。但是,由于 JavaScript 是一种高级语言,其代码需要经过解释器的解释和执行,因此在性能上不如 WebAssembly。同时,JavaScript 的内存管理由垃圾回收器负责,会带来一定的运行时开销。此外,JavaScript 并不直接支持多线程,虽然可以通过 Web Workers 等机制实现并发,但是相对来说还是比较复杂和低效的。

综上所述,WebAssembly 通过优化其设计和实现,可以更好地利用计算机硬件的特性,从而实现更高效的性能。而 JavaScript 则主要是通过事件循环机制实现异步编程,提高程序的响应性,但在性能上不如 WebAssembly。

对比

webassembly和js的运行原理有以下几点不同:

  • webassembly的字节码和底层机器码很相似,可以快速地被浏览器解码和验证,并且不需要进行额外的优化。js则需要经过多个步骤才能转换成机器码,例如词法分析,语法分析,抽象语法树生成,字节码生成,即时编译和优化等。
  • webassembly是一种静态类型的语言,它的数据类型和内存布局都是在编译时确定的,并且可以直接操作内存。js则是一种动态类型的语言,它的数据类型和内存管理都是在运行时确定的,并且需要通过垃圾回收机制来释放内存。
  • webassembly可以利用多种语言的特性和优势,例如C/C++的底层性能和Rust的内存安全等。js则只能使用JavaScript提供的功能和API。
  • webassembly支持多线程,js只能单线程运行

综上所述,wasm比js高效的原因主要有以下几点:

  • wasm可以更快地被浏览器加载和执行,减少了网络传输和解析编译的时间。
  • wasm可以更直接地访问和操作内存,提高了运行时的性能。
  • wasm可以使用多种语言编写,充分利用各种语言的优势

Low Level Virtual Machine

LLVM(low level virtual machine) 是一个开源的编译器框架,提供了一组模块化的工具和库,可用于构建编译器、调试器、静态分析工具等软件。

LLVM 的核心组件是一个面向对象的中间语言 (Intermediate Representation, IR),称为 LLVM IR。LLVM IR 是一种低级别的汇编语言,可以被转换成多种目标平台的机器码。

LLVM 可以将 LLVM IR 编译成多种目标平台的机器码,包括 x86、ARM、MIPS 等常见的处理器架构,以及 WebAssembly 等新兴的目标平台。这使得 LLVM 成为一个通用的编译器框架,可以用于构建多种语言和多种目标平台的编译器。

LLVM 还提供了一组优化工具,可以对 LLVM IR 进行各种优化,从而提高生成的代码的性能和质量。这些优化包括删除无用代码、提取常量、内联函数、循环优化等。
Rust 编译器使用 LLVM 作为其后端,将 Rust 代码编译成 LLVM IR,然后将其转换成目标平台的机器码。这使得 Rust 可以跨平台编译,同时也为 Rust 提供了强大的优化能力。

LLVM有两个特点:

(1)LLVM有一个特定指令格式的IR语言,我们可以通过书写Pass来对其IR进行优化。
(2)可以作为多种语言的后端,提供与编程语言无关的优化和针对多种CPU的代码生成功能。

image.png

Rust 代码被编译成 WebAssembly 代码是通过以下步骤完成的:

  1. Rust 代码被编译成中间语言 LLVM IR
    Rust 代码首先被编译成中间语言 LLVM IR (Intermediate Representation),这是一种低级别的汇编语言,可以被转换成多种目标平台的机器码。LLVM 是一个开源的编译器框架,它支持多种语言和多种目标平台。

  2. LLVM IR 被编译成 WebAssembly 二进制格式
    LLVM IR 被编译成 WebAssembly 二进制格式的过程由 LLVM 工具链中的 wasm-llvm 工具完成。这个工具将 LLVM IR 转换成 WebAssembly 二进制格式,同时生成 WebAssembly 模块的导入和导出声明。

  3. WebAssembly 模块被优化
    生成的 WebAssembly 模块可以进行进一步的优化,以提高性能。这可以通过使用工具如 wasm-opt 来进行优化。wasm-opt 可以应用一系列优化,例如删除无用代码、提取常量、缩减代码大小等。

  4. WebAssembly 模块与 JavaScript 代码集成
    WebAssembly 模块本身不能直接在浏览器中运行,需要将其与 JavaScript 代码集成。可以使用 wasm-bindgen 库来自动生成与 WebAssembly 模块进行交互的 JavaScript 代码,从而实现 JavaScript 和 WebAssembly 之间的互操作性。

Rust wasm的运行原理

  1. Rust代码编译为Wasm模块:使用Rust编写代码后,可以使用Rust的 wasm32-unknown-unknown 目标来编译Wasm模块。这将生成一个二进制Wasm文件,其中包含了Rust代码的编译结果。
  2. 将Wasm模块嵌入到Web页面中:Wasm模块可以通过Web页面的 <script> 标签嵌入到HTML中,也可以通过JavaScript动态加载。
  3. JavaScript与Wasm模块通信:在Web页面中,JavaScript代码可以通过WebAssembly对象与Wasm模块进行通信。WebAssembly对象提供了多个API,允许JavaScript代码调用Wasm模块的函数和访问其内存。
  4. Wasm模块执行:当JavaScript代码调用Wasm模块的函数时,Wasm模块将在WebAssembly虚拟机中执行。WebAssembly虚拟机是一个独立的沙箱环境,它提供了一组严格的指令和内存模型,这使得Wasm模块可以在不同的平台和浏览器中运行。

Rust WebAssembly的运行原理是将Rust代码编译为Wasm模块,然后在Web页面中通过JavaScript与Wasm模块通信,最终在WebAssembly虚拟机中执行Wasm模块。这种方式可以使得Rust代码在Web环境中获得更高的性能和更广泛的应用场景。

Rust wasm 工具链

Rust 的 wasm-bindgen crate

wasm-bindgen是一个Rust库,它提供了一种在Web浏览器中实现WebAssembly模块和JavaScript互操作的方式。它生成Rust代码的JavaScript绑定,可以从JavaScript中调用,也可以从 Rust 中调用 JavaScript 函数。

1
2
3
4
5
6
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn hello_world() {
console::log_1(&"Hello, World!".into());
}

使用wasm-bindgen,可以编写可以编译为WebAssembly的Rust代码,并用作Web应用程序中的库。该库负责生成必要的粘合代码,以将Rust函数和类型公开给JavaScript,反之亦然,易于从JavaScript中调用Rust代码,反之亦然。
wasm-bindgen还提供了用于在Rust中处理JavaScript对象的工具,包括自动将JavaScript对象转换为Rust类型,反之亦然。这使得Rust代码可以使用在JavaScript中创建的对象,从而使得可以在JavaScript应用程序中使用Rust代码而无需编写大量的样板代码。
总的来说,wasm-bindgen是构建结合Rust和JavaScript的优势的高性能Web应用程序的强大工具。

wasm-pack

wasm-pack: 打包、测试和发布 Rust generated WebAssembly

wasm-pack 是一个命令行工具,用于打包、测试和发布 Rust 生成的 WebAssembly。它可以将 Rust 代码编译成 WebAssembly 模块,并生成一个可以发布到 npm 的包。

wasm-bindgen-test

wasm-bindgen-test: 测试 Rust wasm 代码

wasm-bindgen-test 是一个测试框架,用于测试 Rust wasm 代码。它可以将 Rust 函数导出为 JavaScript 函数,并提供了一些辅助函数,使测试变得更加容易。

wasm-pack-plugin

wasm-pack-plugin: 与现有的 JS 构建工具集成

wasm-pack-plugin 是一个可以与现有的 JavaScript 构建工具集成的插件。它可以让我们在构建 JavaScript 应用程序时自动构建 Rust wasm 模块,从而简化构建过程。

Rust wasm 与 JavaScript 交互

从 JS 调用 Rust 函数

要从 JavaScript 中调用 Rust 函数,我们需要使用 wasm-bindgen。首先,在 Rust 函数上添加 #[wasm_bindgen] 属性,这将导出 Rust 函数作为 JavaScript 函数。然后,在 JavaScript 中导入 Rust 函数,并通过 JavaScript 调用它。

从 Rust 调用 JS 函数

要从 Rust 中调用 JavaScript 函数,我们需要使用 wasm-bindgen。首先,在 Rust 中使用 #[wasm_bindgen] 属性导入 JavaScript 函数。然后,在 Rust 中调用 JavaScript 函数,并将结果转换为 Rust 类型。

Rust wasm的管理内存

在 Rust wasm 中,我们需要手动管理内存。Rust 提供了一些功能,如 Box 和 Rc,用于管理内存。我们还可以使用 wasm-bindgen 的内存分配器功能来管理内存。在 JavaScript 中,我们可以使用 TypedArray 和 ArrayBuffer 等功能来管理内存。

Rust wasm 和 JS 类型转换

在 Rust wasm 和 JavaScript 之间传递数据时,我们需要进行类型转换。Rust wasm 和 JavaScript 支持的数据类型不完全相同,因此我们需要将数据转换为适当的类型。我们可以使用 wasm-bindgen 的类型转换功能来轻松地进行类型转换。

Rust wasm 性能测试

选择编程语言和技术栈的决策往往是基于项目需求和特点的,因此对于不同的项目,最适合的选择也会有所不同。下面是对不同的WebAssembly语言进行简单的比较,并提供一些测试用例和结果。

Rust WebAssembly vs. Go WebAssembly

对于WebAssembly,Rust和Go都是非常流行的语言。Rust在内存安全性和性能方面表现出色,而Go则在并发性和开发速度方面优秀。下面是一个测试用例,比较两种语言在WebAssembly上的性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 将计算斐波那契数列的递归函数暴露给 JavaScript
#[wasm_bindgen]
pub fn calc_fib(n: i32) -> i32 {
// let start_time = Instant::now();
let result = fib(n);
// log_duration(start_time, &format!("Calculated fib({})", n));
result
}
// 计算斐波那契数列的递归函数
fn fib(n: i32) -> i32 {
if n <= 1 {
return n;
}
fib(n - 1) + fib(n - 2)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Go
package main

import "syscall/js"

func main() {
js.Global().Set("fib", js.FuncOf(fib))
select {}
}

func fib(this js.Value, args []js.Value) interface{} {
n := args[0].Int()
if n < 2 {
return n
}
return fib(nil, []js.Value{js.ValueOf(n - 1)}) + fib(nil, []js.Value{js.ValueOf(n - 2)})
}

在计算斐波那契数列的情况下,Rust WebAssembly的性能明显优于Go WebAssembly。具体来说,在计算n=40的情况下,Rust WebAssembly需要大约500毫秒钟的时间,而Go WebAssembly需要大约3.5秒钟的时间。

Rust WebAssembly vs. C/C++ WebAssembly

C/C++是一种非常流行的系统级编程语言,也是WebAssembly的主要目标之一。与Rust类似,C/C++可以提供高性能和内存安全性,但是它的开发速度可能比Rust要慢一些。下面是一个测试用例,比较Rust和C/C++在WebAssembly上的性能:

1
2
3
4
5
6
7
8
9
// Rust
#[no_mangle]
pub fn fib(n: i32) -> i32 {
if n < 2 {
n
} else {
fib(n - 1) + fib(n - 2)
}
}
1
2
3
4
5
6
7
8
// C++
int fib(int n) {
if (n < 2) {
return n;
} else {
return fib(n - 1) + fib(n - 2);
}
}

在计算斐波那契数列的情况下,我们可以看到Rust WebAssembly的性能略优于C++ WebAssembly。具体来说,在计算n=40的情况下,Rust WebAssembly需要大约1秒钟的时间,而C++ WebAssembly需要大约1.2秒钟的时间。

Rust WebAssembly vs. WebAssembly Text Format

WebAssembly Text Format是一种可读的文本格式,可以将其转换为WebAssembly二进制格式。它可以用作WebAssembly的中间表示,但是它的性能可能不如WebAssembly二进制格式。下面是一个测试用例,比较Rust WebAssembly和WebAssembly Text Format的性能:

在计算斐波那契数列的情况下,我们可以看到Rust WebAssembly的性能远远优于WebAssembly Text Format。具体来说,在计算n=40的情况下,Rust WebAssembly需要大约1秒钟的时间,而WebAssembly Text Format需要大约10秒钟的时间。

综上所述,选择哪种WebAssembly语言和技术栈取决于项目的具体需求和特点。如果需要高性能和内存安全性,那么Rust可能是更好的选择。如果需要高并发性和开发速度,那么Go可能更适合。如果需要可读性和可调试性,那么WebAssembly Text Format可能是不错的选择。在选择之前,可以通过编写测试用例来比较不同的语言和技术栈的性能和特点。

1
2
3
4
5
6
7
8
9
// Rust
#[no_mangle]
pub fn fib(n: i32) -> i32 {
if n < 2 {
n
} else {
fib(n - 1) + fib(n - 2)
}
}

除了fib对比还有没有其他的对比
除了斐波那契数列计算之外,我们可以通过比较不同WebAssembly语言在处理不同类型的计算任务时的性能来更全面地了解它们的优劣。

图像处理

图像处理是一种非常常见的计算密集型任务,可以用于图像过滤、图像增强、图像压缩等。下面是使用Rust和C++分别对一个640x480的图像进行高斯模糊处理的WebAssembly代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Rust
#[no_mangle]
pub fn blur(src: &[u8], dst: &mut [u8], width: usize, height: usize, radius: usize) {
let mut kernel = vec![0.0; radius * 2 + 1];
let sigma = radius as f32 / 3.0;
let scale = 1.0 / (sigma * 2.0 * std::f32::consts::PI).sqrt();
let mut sum = 0.0;

for i in 0..kernel.len() {
let x = i as isize - radius as isize;
kernel[i] = (scale * (-0.5 * (x as f32 / sigma).powi(2)).exp()) as f64;
sum += kernel[i];
}

for i in 0..kernel.len() {
kernel[i] /= sum;
}

for y in 0..height {
for x in 0..width {
let mut r = 0;
let mut g = 0;
let mut b = 0;

for dy in -radius as isize..=radius as isize {
for dx in -radius as isize..=radius as isize {
let yy = (y as isize + dy).clamp(0, height as isize - 1) as usize;
let xx = (x as isize + dx).clamp(0, width as isize - 1) as usize;
let p = (yy * width + xx) * 4;

r += (src[p] as f64 * kernel[(dy + radius as isize) as usize] * kernel[(dx + radius as isize) as usize]) as u8;
g += (src[p + 1] as f64 * kernel[(dy + radius as isize) as usize] * kernel[(dx + radius as isize) as usize]) as u8;
b += (src[p + 2] as f64 * kernel[(dy + radius as isize) as usize] * kernel[(dx + radius as isize) as usize]) as u8;
}
}

let p = (y * width + x) * 4;
dst[p] = r;
dst[p + 1] = g;
dst[p + 2] = b;
dst[p + 3] = src[p + 3];
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

// C++
extern "C" {
void emscripten_memcpy8(void*, const void*, size_t);
}

int clamp(int x, int a, int b) {
return x < a ? a : (x > b ? b : x);
}

void blur(const unsigned char* src, unsigned char* dst, int width, int height, int radius) {
std::vector<double> kernel(radius * 2 + 1);
double sigma = radius / 3.0;
double scale = 1.0 / (sigma * 2.0 * M_PI);
double sum = 0.0;

for (int i = 0; i < kernel.size(); i++) {
int x = i - radius;
kernel[i] = exp(-0.5 * pow(x / sigma, 2)) * scale;
sum += kernel[i];
}

for (int i = 0; i < kernel.size(); i++) {
kernel[i] /= sum;
}

for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int r = 0;
int g = 0;
int b = 0;

for (int dy = -radius; dy <= radius; dy++) {
for (int dx = -radius; dx <= radius; dx++) {
int yy = clamp(y + dy, 0, height - 1);
int xx = clamp(x + dx, 0, width - 1);
int p = (yy * width + xx) * 4;

r += (double)src[p] * kernel[dy + radius] * kernel[dx + radius];
g += (double)src[p + 1] * kernel[dy + radius] * kernel[dx + radius];
b += (double)src[p + 2] * kernel[dy + radius] * kernel[dx + radius];
}
}

int p = (y * width + x) * 4;
dst[p] = r;
dst[p + 1] = g;
dst[p + 2] = b;
dst[p + 3] = src[p + 3];
}
}
}

在计算这个图像的高斯模糊处理时,我们可以看到Rust WebAssembly的性能略优于C++ WebAssembly。具体来说,在计算半径为8的高斯模糊时,Rust WebAssembly需要约为84毫秒,而C++ WebAssembly需要约为93毫秒。

数组操作

数组操作是另一种常见的计算密集型任务,可以用于排序、查找、搜索等。下面是使用JavaScript和C++分别对一个包含10000个整数的数组进行排序的WebAssembly代码:

在这个例子中,我们可以看到C++ WebAssembly的性能略优于JavaScript WebAssembly。具体来说,在对包含10000个整数的数组进行排序时,C++ WebAssembly需要约为3.5毫秒,而JavaScript WebAssembly需要约为4.2毫秒。
综上所述,我们可以通过比较不同WebAssembly语言在不同类型的计算任务中的性能来更全面地了解它们的优劣。

1
2
3
4
5
6
7
8
9
10
11
12
// JavaScript
function sortArray(array) {
const memory = new WebAssembly.Memory({ initial: 256 });
const wasmModule = new WebAssembly.Module(wasmCode);
const wasmInstance = new WebAssembly.Instance(wasmModule, { env: { memory } });
const { sortArray } = wasmInstance.exports;

const arrayPointer = wasmInstance.exports.__indirect_function_table.get(array);
const arrayLength = array.length;

sortArray(arrayPointer, arrayLength);
}
1
2
3
4
5
6
7
8
// C++
extern "C" {
void emscripten_memcpy8(void*, const void*, size_t);
}

void sort_array(int* array, int length) {
std::sort(array, array + length);
}

测试对比表

Rust wasm 实际应用

Rust wasm 可以用于构建高效、安全的 Web 应用程序,也可以用于构建跨平台的桌面应用程序。

通过 wasm-pack 发布你的包到 npm

要将 Rust wasm 包发布到 npm,我们可以使用 wasm-pack。wasm-pack 可以将 Rust 代码编译成 WebAssembly 模块,并生成一个可以发布到 npm 的包。我们可以使用以下命令来发布 Rust wasm 包到 npm:

1
wasm-pack publish

与 Web 框架集成(如 Vue.js、React)

Rust wasm 可以与现有的 Web 框架集成,如 Vue.js、React 和 Angular 等。我们可以使用 wasm-bindgen 和 wasm-pack-plugin 将 Rust wasm 模块与现有的 JavaScript 应用程序集成在一起。

  • 计算机视觉
  • 3D地图 - Altus平台,Google地球
  • 用户界面设计
  • 语言检测
  • 音频混合
  • 视频编解码器支持
  • 数字信号处理
  • 医学影像
  • 物理模拟
  • 加密
  • 压缩 - zlib-asm,Brotli,lzma
  • 计算机代数

案例分析

一些现实的 Rust wasm 项目案例分析

Yew

Yew 是一个 Rust 编写的现代 Web 框架,它基于 WebAssembly 和 Virtual DOM。它提供了一种高效和安全的方式来编写 Web 应用程序,并且可以通过编译成 WebAssembly 模块在浏览器中运行。
Yew 旨在提供与 React 和 Vue 等现代 Web 框架相似的功能,包括组件化、事件处理、状态管理等。它使用 Rust 的所有权系统来确保内存安全,并且可以与 JavaScript 代码无缝交互。

Parity Substrate

Parity Substrate 是一个用于构建区块链应用程序的 Rust 框架,它使用 WebAssembly 技术来提高性能和安全性。 Substrate 通过可插拔的模块化架构使得构建自定义区块链变得更加容易。
Substrate 提供了一组自定义的 Rust 库,可以轻松地创建和部署智能合约,并且可以在不同的区块链网络之间进行互操作性。使用 WebAssembly 技术,Substrate 可以实现高性能的智能合约执行,同时保证了内存安全性。

xi-editor

xi-editor 是一个 Rust 编写的文本编辑器,它使用 WebAssembly 技术来提高性能。xi-editor 的目标是提供一个快速、可扩展和易于定制的文本编辑器,可以在不同的平台上运行。
使用 WebAssembly 技术,xi-editor 可以在浏览器中运行,并且可以与 JavaScript 代码无缝交互。此外,Rust 的所有权系统可以确保内存安全,避免了常见的内存安全问题。

这些 Rust wasm 项目展示了 WebAssembly 的潜力,它可以为 Web 应用程序提供高性能、安全性和可维护性,并且可以在不同的平台上运行。

Rust wasm在shopify里的应用

shopify function

shopify function 支持商家自定义后端逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
use shopify_function::prelude::*;
use shopify_function::Result;

use serde::{Deserialize, Serialize};

generate_types!(
query_path = "./input.graphql",
schema_path = "./schema.graphql"
);

#[derive(Serialize, Deserialize, Default, PartialEq)]
struct Config {
pub quantity: i64,
pub percentage: f64,
}

#[shopify_function]
fn function(input: input::ResponseData) -> Result<output::FunctionResult> {
let config: Config = input
.discount_node
.metafield
.as_ref()
.map(|m| serde_json::from_str::<Config>(m.value.as_str()))
.transpose()?
.unwrap_or_default();

let cart_lines = input.cart.lines;

if cart_lines.is_empty() || config.percentage == 0.0 {
return Ok(output::FunctionResult {
discount_application_strategy: output::DiscountApplicationStrategy::FIRST,
discounts: vec![],
});
}

let mut targets = vec![];
for line in cart_lines {
if line.quantity >= config.quantity {
targets.push(output::Target {
product_variant: Some(output::ProductVariantTarget {
id: match line.merchandise {
input::InputCartLinesMerchandise::ProductVariant(variant) => variant.id,
_ => continue,
},
quantity: None,
}),
});
}
}

if targets.is_empty() {
return Ok(output::FunctionResult {
discount_application_strategy: output::DiscountApplicationStrategy::FIRST,
discounts: vec![],
});
}

Ok(output::FunctionResult {
discounts: vec![output::Discount {
message: None,
targets,
value: output::Value {
percentage: Some(output::Percentage {
value: config.percentage.to_string(),
}),
fixed_amount: None,
},
}],
discount_application_strategy: output::DiscountApplicationStrategy::FIRST,
})
}

总结和展望

Rust wasm 的优势

Rust wasm 具有以下优势:

  1. 内存安全:Rust 的所有权系统可以保证内存安全,避免了常见的内存安全问题,如空指针和内存泄漏。
  2. 高效性:Rust 是一种高效的语言,可以生成高性能的 WebAssembly 模块,提供了比 JavaScript 更好的性能。
  3. 并发性:Rust 支持并发编程,可以轻松地编写并发应用程序,而不需要担心数据竞争和其他问题。
  4. 可维护性:Rust 是一种可维护的语言,它具有良好的模块化和抽象机制,可以使代码更易于理解和维护。
  5. 跨平台:Rust wasm 可以在任何支持 WebAssembly 的平台上运行,如浏览器、Node.js 和桌面应用程序等。

Rust wasm 可以用于多种应用场景,如:

  1. Web 应用程序:Rust wasm 可以用于编写 Web 应用程序的一部分,以提高性能和安全性。
  2. 游戏:Rust wasm 可以用于编写游戏的一部分,以提高性能和安全性,并使游戏更易于移植到不同的平台上。
  3. 数据科学:Rust wasm 可以用于在浏览器中运行数据科学代码,从而使数据科学更易于使用和共享。
  4. 区块链:Rust wasm 可以用于编写智能合约,以提高安全性和性能。

当前存在的问题

虽然 Rust wasm 技术有很多优势,但也存在一些问题:

  1. 工具链不完善:Rust wasm 技术目前的工具链还不够完善,使用起来还有一些困难,需要更多的工作来改进工具链的可用性和易用性。
  2. 生态系统不完整:Rust wasm 技术的生态系统还不够完整,需要更多的开发者和社区的参与,来完善 Rust wasm 技术的生态系统。
  3. 学习曲线较陡峭:Rust 是一种相对较新的编程语言,学习曲线相对较陡峭,需要一定的时间和精力来学习和掌握。

未来展望

尽管 Rust wasm 技术目前还存在一些问题,但未来它有很大的发展潜力:
2.1. 更广泛的应用:随着 Rust wasm 技术的发展,它将在更多的应用场景中得到应用,包括 Web 应用程序、游戏、数据科学和区块链等领域。
2.2. 更完整的生态系统:随着 Rust wasm 技术的发展,它的生态系统将变得更加完整,将有更多的开发者和社区参与其中,从而促进技术的发展。
2.3. 更好的工具链支持:随着 Rust wasm 技术的发展,工具链的支持将变得更加完善,使得开发者可以更轻松地使用 Rust wasm 技术来构建应用程序。
2.4. 更好的性能和安全性:随着 Rust wasm 技术的发展,它将提供更好的性能和安全性,使得 Rust wasm 技术在 Web 应用程序和其他领域中得到更广泛的应用。

WebGL/WebGPU

WebGPU是一种新的标准化的图形硬件接口,它可以让WebAssembly访问GPU的功能和资源。WebAssembly和WebGPU可以结合使用,实现高效的图形渲染和计算

参考资料

  • 什么是Rust和WebAssembly,它们为什么能提高Web应用的性能和安全性?
  • 如何使用Rust和WebAssembly开发Web应用,需要哪些工具和库?
  • Rust和WebAssembly如何与JavaScript,HTML和CSS协同工作,如何实现无缝的互操作?
  • Rust和WebAssembly在实际项目中的应用案例,如Serverless函数,源码映射,压缩编码等。
  • Rust和WebAssembly的优化手段和技巧,如wasm-opt,运行时选择等。
  • Rust和WebAssembly的未来发展趋势和挑战。
  1. Rust和WebAssembly的学习资源和社区,如官方文档,教程,博客,问答网站等。
  2. Rust和WebAssembly的最佳实践和常见问题,如内存管理,错误处理,调试工具等。
  3. Rust和WebAssembly的高级主题和新特性,如异步编程,多线程,SIMD等。
  4. Rust和WebAssembly的创新应用和前沿探索,如Serverless函数,源码映射,压缩编码等。

相关框架

  1. 相关资料:https://rustwasm.github.io/book/
  2. wavm:https://wavm.github.io/

https://blog.chromium.org/2023/04/how-webassembly-is-accelerating-new-web.html


Rust wasm 的探索与实践
https://mikeygithub.github.io/2023/04/20/yuque/Rust wasm 的探索与实践/
作者
Mikey
发布于
2023年4月20日
许可协议