前言
公眾號:【可樂前端】,期待關注交流,分享一些有意思的前端知識
之前在一個自己的項目中嘗試做一個web視頻轉碼功能,計劃用的是ffmpeg這個強大的庫。當時就瞭解到了wasm把ffmpeg移植到瀏覽器中使用。但是等真正要發佈到生產的時候還是遇到一些問題,
比如説ffmpeg體積比較大,加載速度緩慢;還有sharedArrayBuffer與ffmpeg.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提供了接近底層語言(如C和C++)的性能,同時保持了高級語言的抽象特性。零成本抽象的設計意味着你可以高效地控制硬件,而不會損失性能。 - 併發性:
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中填入以下的內容
[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。
-
libcrate-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文件,利用rust的md5庫來計算文件的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就可以開始打包。
打包出來的產物如下
Vite引入使用
打包好wasm模塊之後,我們就可以將其引入到項目中使用了,這裏我以vite搭建的工程為例,介紹如何把wasm模塊引入到項目之中使用。Vite的配置文件如下,重點需要關注的是vite-plugin-wasm和vite-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();
接下來就可以上傳文件並計算文件的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計算MD5和Rust計算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無論在任何文件體積下,速度都比JS快5倍左右,看到這個結果我不禁感慨,Rust竟恐怖如斯。
最後
本文以計算MD5為場景,介紹了Rust打包Wasm產物並引入到Vite中使用的一種方式,純屬拋磚引玉。如果你有其他想法,歡迎評論區或私信交流,如果覺得有趣的話,點點關注點點贊吧~
推薦閲讀
- 🔥Electron打造你自己的錄屏軟件🔥
- 🖥️Electron實現錄屏軟件(二)——指定區域錄製
- 🐲龍年大吉——AIGC生成龍年春聯🐲
- 🌟前端使用Lottie實現炫酷的開關效果🌟
- 🚀React+Node全棧無死角解析,吃透文件上傳的各個場景