自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

基于IM場景下的Wasm初探:提升Web應(yīng)用性能

開發(fā) 前端
在大多數(shù)場景下我們都不需要用到WebAssembly。因為V8等JS引擎的優(yōu)化帶來了巨大的性能提升,已經(jīng)足夠讓JavaScript應(yīng)對絕大多數(shù)的普通場景了,如果要做進(jìn)一步優(yōu)化密集計算任務(wù)時使用Web worker也都能解決掉。

一、何為Wasm ?

Wasm,全稱 WebAssembly,官網(wǎng)描述是一種用于基于堆棧的虛擬機(jī)的二進(jìn)制指令格式。Wasm被設(shè)計為一個可移植的目標(biāo),用于編譯C/C++/Rust等高級語言,支持在Web上部署客戶端和服務(wù)器應(yīng)用程序。

Wasm 的開發(fā)者參考文檔:https://developer.mozilla.org/en-US/docs/WebAssembly

簡單的來說就是使用C/C++/Rust等語言編寫的代碼,經(jīng)過編譯后得到匯編指令,再通過JavaScript相關(guān)API將文件加載到Web容器中,一句話解釋就是運行在Web容器中的匯編代碼。Wasm是一種可移植、體積小、加載快速的二進(jìn)制格式,可以將各種編程語言的代碼編譯成Wasm模塊,這些模塊可以在現(xiàn)代瀏覽器中直接運行。尤其在涉及到GPU或CPU計算時優(yōu)勢相對比較明顯。

二、為什么需要Wasm ?

JavaScript是解釋型語言,相比于編譯型語言需要在運行時轉(zhuǎn)換,所以解釋型語言的執(zhí)行速度要慢于編譯型語言。

編譯型語言和解釋型語言代碼執(zhí)行的大致流程如下:

圖片圖片

如上流程圖所示,解釋型語言每次執(zhí)行都需要把源碼轉(zhuǎn)換一次才能執(zhí)行,而轉(zhuǎn)換過程非常耗費時間和性能,所以在 JavaScript背景下,Web執(zhí)行一些高性能應(yīng)用是非常困難的,如視頻剪輯、3D游戲等。

Wasm具有緊湊的二進(jìn)制格式,可以接近原生的性能運行,并為C/C++等語言提供一個編譯目標(biāo),以便它們可以在Web上運行。被設(shè)計為可以與JavaScript共存,允許兩者一起工作。在特定的業(yè)務(wù)場景下可以完美的彌補(bǔ)JavaScript的缺陷。

三、優(yōu)勢和限制

優(yōu)勢:

  • 性能優(yōu)異:相比JavaScript代碼,Wasm使用節(jié)省內(nèi)存,快速加載和解釋的二進(jìn)制代碼,具備更快執(zhí)行速度,它是直接在底層虛擬機(jī)中運行的。這使得Web應(yīng)用程序可以更高效地處理復(fù)雜的計算任務(wù),例如圖形渲染、物理模擬等。
  • 跨平臺兼容:Wasm可以在幾乎所有現(xiàn)代瀏覽器中運行,兼容性可參考caniuse,無論是桌面還是移動設(shè)備。這意味著開發(fā)者可以使用各種編程語言來編寫Web應(yīng)用程序,而不僅僅局限于JavaScript。
  • 安全性:Wasm運行在沙箱環(huán)境中,提供了良好的安全性。使用了一系列安全措施,如內(nèi)存隔離和沙箱限制,以防止惡意代碼對系統(tǒng)的攻擊。
  • 模塊化:Wasm模塊可以作為獨立的組件進(jìn)行開發(fā)和部署,開發(fā)者可以更好地管理和維護(hù)代碼庫。模塊化的設(shè)計也為將來的性能優(yōu)化和增量更新提供了便利。

局限性:

  • 生態(tài)系統(tǒng)不夠完善:盡管Wasm已經(jīng)成為Web開發(fā)中的關(guān)鍵技術(shù)之一,但生態(tài)系統(tǒng)仍然不夠完善。Wasm的工具、框架和庫的數(shù)量遠(yuǎn)不如JavaScript。
  • 開發(fā)門檻較高:Wasm的開發(fā)門檻相對較高。Wasm需要使用一種新的語言來編寫,如C或C++等。這使得學(xué)習(xí)和使用Wasm的成本相對較高。尤其是在內(nèi)存管理等方面會增加開發(fā)的復(fù)雜性。
  • 與JavaScript集成問題:Wasm與JavaScript之間的集成問題是一個挑戰(zhàn)。開發(fā)人員需要解決如何在Web應(yīng)用程序中同時使用Wasm和JavaScript的問題。
  • 兼容性問題:雖然現(xiàn)代瀏覽器已經(jīng)開始支持Wasm,但是在一些老舊的瀏覽器中可能存在兼容性問題,需要開發(fā)者進(jìn)行額外的處理來確保代碼的兼容性。

四、Wasm工作原理

通過上述的編譯型語言和解釋型語言代碼執(zhí)行的大致流程我們可以知道Wasm是不需要被解釋的,是由開發(fā)者提前編譯為WebAssembly二進(jìn)制格式,如下圖所示。由于變量類型都是預(yù)知的,因此瀏覽器加載WebAssembly文件時,JavaScript引擎無須監(jiān)測代碼。它可以簡單地將這段代碼的二進(jìn)制格式編譯為機(jī)器碼。

圖片圖片

從這個流程中我們也可以看出,如果將每種編程語言都直接編譯為機(jī)器碼的各個版本,這樣效率是不是更高呢?想法是好的,但實現(xiàn)過程確實復(fù)雜不堪的。由于瀏覽器是可以在若干不同的處理器 (比如手機(jī)和平板等設(shè)備) 上運行,因此為每個可能的處理器發(fā)布一個WebAssembly代碼的編譯后版本會很難做到。

我們可以通過替代方法即取得IR代碼。IR即為中間代碼(Intermediate Representation),它是編譯器中很重要的一種數(shù)據(jù)結(jié)構(gòu)。編譯器在做完前端工作以后,首先就生成IR,并在此基礎(chǔ)上執(zhí)行各種優(yōu)化算法,最后再生成目標(biāo)代碼??梢院喕癁槿缦铝鞒蹋?/p>

圖片圖片

編譯器將IR代碼轉(zhuǎn)換為一種專用字節(jié)碼并放入后綴為.wasm的文件中。此時Wasm文件中的字節(jié)碼還不是機(jī)器碼,它只是支持WebAssembly的瀏覽器能夠理解的一組虛擬指令。當(dāng)加載到支持WebAssembly的瀏覽器中時,瀏覽器會驗證這個文件的合法性,然后這些字節(jié)碼會繼續(xù)編譯為瀏覽器所運行的設(shè)備上的機(jī)器碼。

更加詳情的原理和使用方式可以前往https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface查閱。

五、應(yīng)用場景

在Web開發(fā)中,可以使用Wasm來提高應(yīng)用程序的性能。以下是一些使用Wasm的常見場景:

  • 高性能計算:如果應(yīng)用程序需要進(jìn)行大量的數(shù)值計算、圖像處理或者復(fù)雜的算法運算,可以將這部分代碼編譯成Wasm模塊,以提高計算性能。
  • 游戲開發(fā):Wasm可以用于創(chuàng)建高性能的HTML5游戲,通過將游戲邏輯編譯成Wasm模塊,可以實現(xiàn)更流暢的游戲體驗。
  • 跨平臺應(yīng)用:使用Wasm可以實現(xiàn)跨平臺的應(yīng)用程序,無論是桌面還是移動設(shè)備,用戶都可以通過瀏覽器來訪問和使用。
  • 移植現(xiàn)有代碼:如果已經(jīng)有用其他編程語言編寫的代碼,可以通過將其編譯成Wasm模塊,將其集成到現(xiàn)有的Web應(yīng)用程序中,而無需重寫整個應(yīng)用程序。

六、產(chǎn)品案例

  • 設(shè)計工具Figma-Wasm文件大小為27.7M

圖片圖片

  • Google Earth-Wasm文件總計大小為192.M
  • 支持各大瀏覽器的3D地圖,而且運行流暢
  • B站-視頻處理和播放也有使用Wasm,Wasm文件大小為344kb

圖片圖片

  • 跨平臺的OpenGL圖形引擎Magnum-Wasm文件大小為844kb

圖片圖片

七、實踐案例

這里我們通過使用Rust + Wasm實現(xiàn)Wasm與JavaScript之間的數(shù)據(jù)調(diào)用,理解Rust和Wasm的交互過程。

使用Rust就需要做一些前置的環(huán)境配置,詳情的步驟可參考Rust官網(wǎng):

https://www.rust-lang.org/zh-CN/tools/install。

安裝wasm-pack,wasm-pack是一個構(gòu)建、測試和發(fā)布Wasm的Rust CLI工具,我們將使用wasm-pack相關(guān)的命令來構(gòu)建Wasm二進(jìn)制內(nèi)容。這有助于將代碼編譯為WebAssembly,并生成在瀏覽器中使用的正確包。

Rust項目初始化

執(zhí)行cargo new rust_wasm初始化Rust項目,自動生成配置文件Cargo.toml,項目結(jié)構(gòu)如下:

/Users/admin/RustroverProjects/rust_wasm
├── Cargo.lock
├── Cargo.toml
├── src
|  └── lib.rs
└── target
   ├── CACHEDIR.TAG
   └── debug
      ├── build
      ├── deps
      ├── examples
      └── incremental

配置包文件

我們可以在Cargo.toml文件中加上下列代碼并保存,保存之后Cargo會自動下載依賴。

  • crate-type = ["cdylib"],表示編譯時候使用C標(biāo)準(zhǔn)的動態(tài)庫。
  • #[wasm_bindgen]是一個屬性宏,來自于wasm_bindgen這個crate,是一個簡化Rust WASM與JS之間交互的庫。 
[lib]
crate-type = ["cdylib"]


[dependencies]
wasm-bindgen = { version = "0.2.89", features = [] }

 編寫代碼

編寫代碼之前我們先明確Rust中crate包的概念,Rust中包管理系統(tǒng)將crate包分為二進(jìn)制包(Binary)和庫包(Library)兩種,二者可以在同一個項目中同時存在。

二進(jìn)制包:

  • main.rs是二進(jìn)制項目的入口
  • 二進(jìn)制項目可直接執(zhí)行
  • 一個項目中二進(jìn)制包可以有多個,所以在Cargo.toml中通過雙方括號標(biāo)識 [[bin]]

庫包:

  • lib.rs是庫包的入口
  • 庫項目不可直接執(zhí)行,通常用來作為一個模塊被其他項目引用
  • 一個項目中庫包僅有1個,在Cargo.toml中通過單方括號標(biāo)識 [lib]

因為我們這里希望將 Wasm 轉(zhuǎn)為一個可以在JS項目中使用的模塊,所以需要使用庫包 lib.rs 的命名,代碼如下。

use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub extern "C" fn rust_add(left: i32, right: i32) -> i32 {
    println!("Hello from Rust!");
    left + right
}

執(zhí)行編譯

這里我們要使用到wasm-pack,將上述的Rust代碼編譯為能夠被JS導(dǎo)入的模塊,根據(jù)wasm-pack提供的target方式可以指定構(gòu)建的產(chǎn)物,如截圖所示:

圖片圖片

編譯過程效果:

編譯完成后,我們會發(fā)現(xiàn)根目錄下多了一個pkg/ 文件夾,里面就是我們的Wasm產(chǎn)物所在的npm包了。目錄結(jié)構(gòu)如下:

/Users/admin/RustroverProjects/rust_wasm/pkg
├── package.json
├── rust_wasm.d.ts
├── rust_wasm.js
├── rust_wasm_bg.wasm
└── rust_wasm_bg.wasm.d.ts

rust_wasm.d.ts文件內(nèi)容:

/* tslint:disable */
/* eslint-disable */
/**
* @param {number} num
* @returns {string}
*/
export function msg_insert(num: number): string;
/**
* @param {number} left
* @param {number} right
* @returns {number}
*/
export function rust_add(left: number, right: number): number;
/**
*/
export function rust_thread(): void;


export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;


export interface InitOutput {
  readonly memory: WebAssembly.Memory;
  readonly msg_insert: (a: number, b: number) => void;
  readonly rust_add: (a: number, b: number) => number;
  readonly rust_thread: () => void;
  readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
  readonly __wbindgen_free: (a: number, b: number, c: number) => void;
}


export type SyncInitInput = BufferSource | WebAssembly.Module;
/**
* Instantiates the given `module`, which can either be bytes or
* a precompiled `WebAssembly.Module`.
*
* @param {SyncInitInput} module
*
* @returns {InitOutput}
*/
export function initSync(module: SyncInitInput): InitOutput;


/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {InitInput | Promise<InitInput>} module_or_path
*
* @returns {Promise<InitOutput>}
*/
export default function __wbg_init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;

wasm-pack打包不僅輸出一個ESM規(guī)范的模塊,而且還支持自動生成d.ts文件,對模塊的使用者非常友好。如下:

圖片圖片

在前端項目中引入使用

'use client'
/*
 * @Author: wangweiqiang
 * @Date: 2024-06-18 17:03:34
 * @LastEditors: wangweiqiang
 * @LastEditTime: 2024-06-18 23:09:55
 * @Description: app.tsx
 */
import Image from "next/image";
import { useCallback, useEffect, useState } from "react";
import init, * as rustLibrary from 'rust_wasm'
export default function Home() {
  const [addResult, setAddResult] = useState<number | null>(null)
  const [calculateTime, setCalculateTime] = useState<string>('')


  const initRustLibrary = useCallback(() => {
    init().then(() => {
      const result = rustLibrary.rust_add(5, 6)
      const timeStamp = rustLibrary.msg_insert(50000)
      setCalculateTime(timeStamp)
      setAddResult(result)
    })
  }, [])


  useEffect(() => {
    initRustLibrary()
  }, [initRustLibrary]);


  return (
    <main className="flex min-h-screen flex-col items-center p-24">
      {/* .... */}
      <div className="mt-32 grid text-center lg:mb-0 lg:w-full lg:max-w-5xl lg:grid-cols-4 lg:text-left">
        <div>
          rust代碼計算結(jié)果:{addResult}
        </div>
        <div style={{ marginTop: '20px' }}>
          二分法方式{calculateTime}
        </div>
      </div>
    </main>
  );
}

圖片圖片

性能比較

在IM場景下,聊天消息中核心的處理流程在于數(shù)據(jù)的排序、去重,大量的數(shù)據(jù)查找會非常耗時,在這里我們通過二分法的方式對Rust和JavaScript兩種實現(xiàn)方式的耗時進(jìn)行一個簡單的對比,Rust代碼如下:

use chrono::{DateTime, Utc};
use rand::Rng;


#[derive()]
#[allow(dead_code)]
struct Data {
    content: String,
    from: String,
    head: String,
    msg_id: String,
    seq: i32,
    sid: String,
    topic: String,
    ts: DateTime<Utc>,
}


impl Data {
    fn new(
        content: String,
        from: String,
        head: String,
        msg_id: &str,
        seq: i32,
        sid: String,
        topic: String,
        ts: DateTime<Utc>,
    ) -> Self {
        Data {
            content,
            from,
            head,
            msg_id: msg_id.to_string(),
            seq,
            sid,
            topic,
            ts,
        }
    }
}


// 獲取原始數(shù)據(jù)
fn get_origin_data(num: i32) -> Vec<Data> {
    let mut data: Vec<Data> = vec![]; // 存儲數(shù)據(jù)的向量
    ....                              // 創(chuàng)建 num 個數(shù)據(jù)
    data
}
// 初始化結(jié)構(gòu)體數(shù)據(jù)
fn init_struct_data(num: i32, text: &str) -> Data {
    let mut rng = rand::thread_rng();
    let content = format!("{}_{}", rng.gen_range(1000..=9999), text).to_string();
    ....
    let ts = Utc::now();
    Data::new(content, from, head, &msg_id.as_str(), seq, sid, topic, ts)
}


// 二分法插入
fn binary_insert(data: &mut Vec<Data>, new_data: Data) {
    let _insert_pos = match data.binary_search_by_key(&new_data.seq, |d| d.seq) {
        Ok(pos) => {
            data[pos] = new_data;
            pos
        }
        Err(pos) => {
            data.insert(pos, new_data);
            pos
        }
    };
}
#[wasm_bindgen]
pub extern "C" fn msg_insert(num: i32) -> String {
    let mut data: Vec<Data> = get_origin_data(1000);
    let test_mode = [num];
    let start_time = Utc::now().naive_utc().timestamp_micros();
    for test_num in 0..test_mode.len() {
        for num in 0..test_mode[test_num] {
            let data_list = init_struct_data(num, "test");
            binary_insert(&mut data, data_list);
        }
    }
    let duration = Utc::now().naive_utc().timestamp_micros() - start_time;
    let result = format!("插入{}條數(shù)據(jù)執(zhí)行耗時:{}微秒", num, duration);
    result
}

數(shù)據(jù)對比分析:

圖片圖片

可以看到,在數(shù)據(jù)量不大的場景下,Wasm的耗時是比純JavaScript長的,這是因為瀏覽器需要在VM容器中對 Wasm模塊進(jìn)行實例化,這一部分會消耗相當(dāng)?shù)臅r間,導(dǎo)致性能不如純JavaScript的執(zhí)行。但隨著運算規(guī)模變大,Wasm的優(yōu)化越來越明顯。這是因為WebAssembly是一種低級別的二進(jìn)制格式,經(jīng)過高度優(yōu)化,并且能夠更好地利用系統(tǒng)資源。相比之下,JavaScript是一種解釋性語言,性能可能會受到解釋器的限制。

八、總結(jié)

在大多數(shù)場景下我們都不需要用到WebAssembly。因為V8等JS引擎的優(yōu)化帶來了巨大的性能提升,已經(jīng)足夠讓JavaScript應(yīng)對絕大多數(shù)的普通場景了,如果要做進(jìn)一步優(yōu)化密集計算任務(wù)時使用Web worker也都能解決掉。只有在以上的少數(shù)場景下,我們才需要做這種“二次提升”。

WebAssembly雖然有天然的優(yōu)勢,但也有自己的局限性,在使用時我們也需要考慮多方面因素,例如生態(tài)、開發(fā)成本等等。不過我們依然可以持續(xù)關(guān)注WebAssembly的發(fā)展。

責(zé)任編輯:武曉燕 來源: 得物技術(shù)
相關(guān)推薦

2017-12-13 13:09:36

NginxWeb應(yīng)用

2013-01-14 12:24:06

Firefox OS

2014-08-26 15:02:04

mAPM移動應(yīng)用性能監(jiān)測AppDynamics

2018-08-23 17:45:52

2013-08-09 14:18:33

2015-12-14 10:39:14

2023-11-06 08:01:09

Go同步異步

2024-12-10 08:09:15

2023-11-07 11:11:42

Go性能

2023-09-04 07:30:03

Wasm匯編語言

2014-04-24 10:11:17

iOS性能調(diào)優(yōu)

2009-10-14 20:37:41

sun閃存固態(tài)硬盤

2009-07-17 19:09:42

虛擬化VMware服務(wù)器

2009-07-16 10:57:04

虛擬化新功能性能

2010-08-25 09:48:14

W3CWeb性能工作組

2010-04-02 15:20:44

惠普成功案例

2009-08-25 15:35:45

citrxinetscalerncore

2018-02-03 10:16:05

JavaScript Web 應(yīng)用

2021-08-09 16:39:52

工具JVM剖析

2011-09-20 10:41:45

Web
點贊
收藏

51CTO技術(shù)棧公眾號