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

简介: 🚀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已经安装完毕:

211.png

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

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

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


计算文件MD5

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

[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数组,输出是一个字符串

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就可以开始打包。

323.png

打包出来的产物如下 343.png

Vite引入使用

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

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文件夹放到前端项目下,用以下的方式引入:

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

444.png


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

  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倍左右,看到这个结果我不禁感慨,Rust竟恐怖如斯。

最后

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


相关文章
|
3月前
|
JavaScript API
深入探索fs.WriteStream:Node.js文件写入流的全面解析
深入探索fs.WriteStream:Node.js文件写入流的全面解析
|
14天前
|
JavaScript 前端开发
|
2月前
|
JavaScript
js计算时间差,包括计算,天,时,分,秒
js计算时间差,包括计算,天,时,分,秒
249 16
|
1月前
|
JavaScript 前端开发 内存技术
js文件的入口代码及需要入口代码的原因
js文件的入口代码及需要入口代码的原因
35 0
|
2月前
|
前端开发 JavaScript API
前端JS读取文件内容并展示到页面上
前端JavaScript使用FileReader API读取文件内容,支持文本类型文件。在文件读取成功后,可以通过onload事件处理函数获取文件内容,然后展示到页面上。
85 2
前端JS读取文件内容并展示到页面上
|
1月前
|
缓存 JavaScript 前端开发
探索Vue.js中的计算属性与侦听器
【10月更文挑战第5天】探索Vue.js中的计算属性与侦听器
22 1
|
2月前
|
JavaScript 前端开发 数据安全/隐私保护
混淆指定js文件
【9月更文挑战第26天】JavaScript 混淆旨在保护代码知识产权、减小文件体积和提高安全性。方法包括变量名和函数名混淆、代码压缩、控制流平坦化及字符串加密。常用工具如 UglifyJS 和 JScrambler 可实现这些功能。然而,混淆可能带来兼容性和调试困难等问题,需谨慎使用并确保法律合规。
|
1月前
|
JavaScript 前端开发 应用服务中间件
vue前端开发中,通过vue.config.js配置和nginx配置,实现多个入口文件的实现方法
vue前端开发中,通过vue.config.js配置和nginx配置,实现多个入口文件的实现方法
141 0
|
2月前
|
移动开发 JavaScript 前端开发
js之操作文件| 12-5
js之操作文件| 12-5
|
1月前
|
缓存 JavaScript 前端开发
深入理解Vue.js中的计算属性与侦听属性
【10月更文挑战第5天】深入理解Vue.js中的计算属性与侦听属性
25 0