🚀JS使用Wasm为你的文件MD5计算装上火箭引擎🚀

前言

之前在一个自己的项目中尝试做一个web视频转码功能,计划用的是ffmpeg这个强大的库。当时就了解到了wasmffmpeg移植到浏览器中使用。但是等真正要发布到生产的时候还是遇到一些问题,

比如说ffmpeg体积比较大,加载速度缓慢;还有sharedArrayBufferffmpeg.wasm的一些关系,简单来说就是如果需要使用多线程版的ffmpeg,就需要设置COOP/COEP这两个新的跨域策略,但是设置这两个东西就会破坏OAuth的集成;或者可以选择使用单线程的ffmpeg,但是效率感人。。

但是这并不影响将C/C++/Rust等语言编译成Wasm移植到浏览器依旧是一种很有魅力的解决方案,今天一起来走进它吧!

Wasm简介

WebAssembly(Wasm)是一种开放标准,旨在提供一种可移植、高性能的二进制格式,用于在web浏览器中运行。它不是特定于任何语言的,而是为多种编程语言设计,包括C、C++、Rust等。通过将代码编译为Wasm格式,开发人员可以实现在不同平台和浏览器上运行的一致性性能。

Wasm的主要目标之一是提供比传统的JavaScript更高效的执行速度。它允许开发人员使用其他语言编写部分应用程序,然后将这些部分集成到web应用程序中,实现更好的性能和更广泛的语言选择。

此外,Wasm还提供了安全性、可移植性和版本控制等方面的优势。它在web浏览器中作为一个虚拟机执行,与浏览器的JavaScript引擎紧密集成,使得web应用程序可以更高效地利用底层硬件资源。

Hello Rust

Rust是一种系统级编程语言,注重内存安全、并发性和性能。由Mozilla开发,使用它可以高效地控制硬件,同时保持高级语言的安全性。具体有以下比较突出的特点:

  • 内存安全 : Rust通过所有权系统、生命周期检查和借用机制,有效地防止了空指针引用、数据竞争和内存泄漏等内存安全问题,使得编写安全的并发代码更为容易。
  • 性能 : Rust提供了接近底层语言(如CC++)的性能,同时保持了高级语言的抽象特性。零成本抽象的设计意味着你可以高效地控制硬件,而不会损失性能。
  • 并发性 : Rust通过所有权系统和借用机制,支持并发编程,同时避免了常见的并发错误。这使得开发者能够编写线程安全的代码,而不需要额外的锁或同步原语。
  • 生态系统 : Rust拥有一个不断壮大的生态系统,有丰富的库和工具,涵盖了各种应用场景。这使得开发者能够更容易地构建各种类型的应用,从系统级应用到Web服务。
  • 开发者友好 : Rust的语法清晰、现代化,拥有友好的文档和社区支持。它鼓励编写易读易维护的代码,同时提供了丰富的工具链和调试支持。

Rust的具体安装方式可以参考这个文档:Rust安装,如果你是Mac用户,看到下图的时候表示Rust已经安装完毕:

安装Rust的时候一般情况下会自带安装cargo,它是Rust的库管理工具,类似于npm。我们可以使用 cargo new hello_rust来创建一个Rust项目。

项目安装好之后结构目录大致如上,如果你是使用vscode进行开发的话,建议安装rust-analyzer这个插件,它提供了代码的语法分析、自动完成、错误分析等功能,可以大大的提升我们的开发效率。

下面执行一下cargo run命令,就可以把我们的Rust项目跑起来:

计算文件MD5

首先使用cargo new 来创建一个Rust项目,在Cargo.toml中填入以下的内容

ini 复制代码
[package]
name = "rust_md5"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
md-5 = "0.9.0"
js-sys = "0.3.50"
wasm-bindgen = "0.2.73"

[profile.release]
opt-level = 3
lto = true
strip = true
panic = "abort"

解释一下上面的字段:

  • package :包的相关信息
    • name:指定了你的项目的名称。每个 Rust 项目都有一个唯一的名称。
    • version:指定了你的项目的版本号。这遵循语义化版本规范(Semantic Versioning),通常包括主版本号、次版本号和修订号。
    • edition:指定了 Rust 编译器所使用的语言版本。在这里,它指定了项目使用 Rust 2021 Edition。
  • lib
    • crate-type :这里指定了生成的 crate 的类型为动态链接库(.cdylib),这通常用于构建 WebAssembly 模块。
  • dependencies 依赖项
  • [profile.release] :关于 release 模式的配置。
    • opt-level = 3:指定了编译器的优化级别。在 release 模式下,通常选择最高级别(3),以便进行更强大的优化。
    • lto = true:启用 Link Time Optimization(LTO),这允许在链接阶段进行更广泛的优化。
    • strip = true:启用在编译结束后去除调试信息和未使用的代码等优化,以减小生成的二进制文件的大小。
    • panic = "abort" :指定了在 release 模式下发生 panic 时的处理方式。这里设置为 "abort",表示在发生 panic 时立即终止程序。

然后在src文件夹下新增一个lib.rs文件,利用rustmd5库来计算文件的md5,其中输入是uint8数组,输出是一个字符串

rs 复制代码
use md5::{Digest, Md5};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;

#[wasm_bindgen]
#[repr(C)]
pub struct RustMd5 {
}

#[wasm_bindgen]
impl RustMd5 {
    pub fn new() -> Self {
        RustMd5 {
        }
    }

    pub fn calculate_md5(&self, file_buffer: &[u8]) -> Result<String, JsValue> {
        let mut md5 = Md5::new();
        md5.update(file_buffer);

        let result = md5.finalize();

        let md5_string = result
            .iter()
            .map(|b| format!("{:02x}", b))
            .collect::<String>();

        Ok(md5_string)
    }
}

打包Wasm

接下来我们就可以把这个rust工程来打包成wasm产物,使用到的是wasm-pack这个工具,首先可以使用cargo install wasm-pack来安装这个打包工具,然后执行wasm-pack build就可以开始打包。

打包出来的产物如下

Vite引入使用

打包好wasm模块之后,我们就可以将其引入到项目中使用了,这里我以vite搭建的工程为例,介绍如何把wasm模块引入到项目之中使用。Vite的配置文件如下,重点需要关注的是vite-plugin-wasmvite-plugin-top-level-await这两个包,记得提前安装好。

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

然后把打包好的pkg文件夹放到前端项目下,用以下的方式引入:

js 复制代码
import * as wasm from "./pkg/rust_md5.js";
console.log(wasm);
const rustMd5 = wasm.RustMd5.new();

接下来就可以上传文件并计算文件的MD5了,这里需要注意的是我们写的Rust模块计算MD5的方法接受的是一个Uint8Array,所以前端需要转换一下再传输给Rust,示例代码如下:

js 复制代码
  const handleFileChange = (e) => {
    const uploadedFile = e.target.files[0];

    // 使用 FileReader 读取文件并转换为 ArrayBuffer
    const fileReader = new FileReader();
    console.log("读取文件并转换为 ArrayBuffer");
    fileReader.onload = function (e) {
      // 获取 ArrayBuffer
      const arrayBuffer = e.target.result;
      const startTime = performance.now();
      const uint8Array = new Uint8Array(arrayBuffer);
      const res = rustMd5.calculate_md5(uint8Array);
      console.log("res", res);
      const endTime = performance.now();
      const executionTime = (endTime - startTime) / 1000; // 单位:秒
      alert(executionTime + "s");
    };

    // 以 ArrayBuffer 格式读取文件
    fileReader.readAsArrayBuffer(uploadedFile);
  };
 
// 省略一些代码
<input type="file" onChange={handleFileChange} />

对比JS

我使用了几种规格的文件大小,分别对JS计算MD5Rust计算MD5的速度进行了对比,我的测试笔记本是Apple M1芯片,8G内存。

结果如下,单位为秒

文件大小 300K 1.5M 15M 125M 2G
Rust 0.0036 0.0040 0.032 0.2635 5.28
Js 0.0124 0.028 0.16 1.26 21.148

从上面可以看出,Rust无论在任何文件体积下,速度都比JS5-20倍不等,看到这个结果我不禁感慨,Rust竟恐怖如斯。

最后

本文以计算MD5为场景,介绍了Rust打包Wasm产物并引入到Vite中使用的一种方式,纯属抛砖引玉。如果你有其他想法,欢迎评论区或私信交流,如果觉得有趣的话,点点关注点点赞吧~

推荐阅读

相关推荐
程序媛-徐师姐37 分钟前
Java 基于SpringBoot+vue框架的老年医疗保健网站
java·vue.js·spring boot·老年医疗保健·老年 医疗保健
Myli_ing1 小时前
考研倒计时-配色+1
前端·javascript·考研
余道各努力,千里自同风1 小时前
前端 vue 如何区分开发环境
前端·javascript·vue.js
PandaCave1 小时前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
软件小伟1 小时前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾2 小时前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧2 小时前
TypeScript 的发展与基本语法
前端·javascript·typescript
chusheng18402 小时前
Java项目-基于SpringBoot+vue的租房网站设计与实现
java·vue.js·spring boot·租房·租房网站
游走于计算机中摆烂的2 小时前
启动前后端分离项目笔记
java·vue.js·笔记
幼儿园的小霸王3 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue