博客 / 詳情

返回

🚀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已經安裝完畢:

image.png

安裝Rust的時候一般情況下會自帶安裝cargo,它是Rust的庫管理工具,類似於npm。我們可以使用 cargo new hello_rust來創建一個Rust項目。

image.png

項目安裝好之後結構目錄大致如上,如果你是使用vscode進行開發的話,建議安裝rust-analyzer這個插件,它提供了代碼的語法分析、自動完成、錯誤分析等功能,可以大大的提升我們的開發效率。

下面執行一下cargo run命令,就可以把我們的Rust項目跑起來:

image.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就可以開始打包。

image.png

打包出來的產物如下

image.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();

image.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中使用的一種方式,純屬拋磚引玉。如果你有其他想法,歡迎評論區或私信交流,如果覺得有趣的話,點點關注點點贊吧~

推薦閲讀

  • 🔥Electron打造你自己的錄屏軟件🔥
  • 🖥️Electron實現錄屏軟件(二)——指定區域錄製
  • 🐲龍年大吉——AIGC生成龍年春聯🐲
  • 🌟前端使用Lottie實現炫酷的開關效果🌟
  • 🚀React+Node全棧無死角解析,吃透文件上傳的各個場景
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.