百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Wasm在即时通讯IM场景下的Web端应用性能提升初探

zhezhongyun 2025-01-24 14:48 39 浏览

本文由得物技术WWQ分享,原题“基于IM场景下的Wasm初探:提升Web应用性能”,下文进行了排版和内容优化。

1、什么是Wasm

Wasm,全称 WebAssembly,官网描述是一种用于基于堆栈的虚拟机的二进制指令格式。Wasm被设计为一个可移植的目标,用于编译C/C++/Rust等高级语言,支持在Web上部署客户端和服务器应用程序。

2、Wasm有什么用

JavaScript是解释型语言,相比于编译型语言需要在运行时转换,所以解释型语言的执行速度要慢于编译型语言。

编译型语言和解释型语言代码执行的大致流程如下:

Wasm具有紧凑的二进制格式,可以接近原生的性能运行,并为C/C++等语言提供一个编译目标,以便它们可以在Web上运行。被设计为可以与JavaScript共存,允许两者一起工作。在特定的业务场景下可以完美的弥补JavaScript的缺陷。

3、Wasm的优势和限制

优势:

1)性能优异:相比JavaScript代码,Wasm使用节省内存,快速加载和解释的二进制代码,具备更快执行速度,它是直接在底层虚拟机中运行的。这使得Web应用程序可以更高效地处理复杂的计算任务,例如图形渲染、物理模拟等;

2)跨平台兼容:Wasm可以在几乎所有现代浏览器中运行,兼容性可参考caniuse,无论是桌面还是移动设备。这意味着开发者可以使用各种编程语言来编写Web应用程序,而不仅仅局限于JavaScript;

3)安全性:Wasm运行在沙箱环境中,提供了良好的安全性。使用了一系列安全措施,如内存隔离和沙箱限制,以防止恶意代码对系统的攻击;

4)模块化:Wasm模块可以作为独立的组件进行开发和部署,开发者可以更好地管理和维护代码库。模块化的设计也为将来的性能优化和增量更新提供了便利。

局限性:

1)生态系统不够完善:尽管Wasm已经成为Web开发中的关键技术之一,但生态系统仍然不够完善。Wasm的工具、框架和库的数量远不如JavaScript;

2)开发门槛较高:Wasm的开发门槛相对较高。Wasm需要使用一种新的语言来编写,如C或C++等。这使得学习和使用Wasm的成本相对较高。尤其是在内存管理等方面会增加开发的复杂性;

3)与JavaScript集成问题:Wasm与JavaScript之间的集成问题是一个挑战。开发人员需要解决如何在Web应用程序中同时使用Wasm和JavaScript的问题;

4)兼容性问题:虽然现代浏览器已经开始支持Wasm,但是在一些老旧的浏览器中可能存在兼容性问题,需要开发者进行额外的处理来确保代码的兼容性。

4、Wasm工作原理

通过上述的编译型语言和解释型语言代码执行的大致流程我们可以知道Wasm是不需要被解释的,是由开发者提前编译为WebAssembly二进制格式(如下图所示)。

由于变量类型都是预知的,因此浏览器加载WebAssembly文件时,JavaScript引擎无须监测代码。它可以简单地将这段代码的二进制格式编译为机器码。

从这个流程中我们也可以看出:如果将每种编程语言都直接编译为机器码的各个版本,这样效率是不是更高呢?想法是好的,但实现过程确实复杂不堪的。由于浏览器是可以在若干不同的处理器(比如手机和平板等设备)上运行,因此为每个可能的处理器发布一个WebAssembly代码的编译后版本会很难做到。

我们可以通过替代方法即取得IR代码。IR即为中间代码(Intermediate Representation),它是编译器中很重要的一种数据结构。编译器在做完前端工作以后,首先就生成IR,并在此基础上执行各种优化算法,最后再生成目标代码。

可以简化为如下流程:

编译器将IR代码转换为一种专用字节码并放入后缀为.wasm的文件中。此时Wasm文件中的字节码还不是机器码,它只是支持WebAssembly的浏览器能够理解的一组虚拟指令。当加载到支持WebAssembly的浏览器中时,浏览器会验证这个文件的合法性,然后这些字节码会继续编译为浏览器所运行的设备上的机器码。

更加详情的原理和使用方式可以前往https://developer.mozilla.org/en ... avaScript_interface查阅。

5、Wasm应用场景

在Web开发中,可以使用Wasm来提高应用程序的性能。

以下是一些使用Wasm的常见场景:

  • 1)高性能计算:如果应用程序需要进行大量的数值计算、图像处理或者复杂的算法运算,可以将这部分代码编译成Wasm模块,以提高计算性能;
  • 2)游戏开发:Wasm可以用于创建高性能的HTML5游戏,通过将游戏逻辑编译成Wasm模块,可以实现更流畅的游戏体验;
  • 3)跨平台应用:使用Wasm可以实现跨平台的应用程序,无论是桌面还是移动设备,用户都可以通过浏览器来访问和使用;
  • 4)移植现有代码:如果已经有用其他编程语言编写的代码,可以通过将其编译成Wasm模块,将其集成到现有的Web应用程序中,而无需重写整个应用程序。

6、Wasm的应用案例

1)设计工具Figma-Wasm文件大小为27.7M:

7、得物的Wasm实践

7.1准备

这里我们通过使用Rust + Wasm实现Wasm与JavaScript之间的数据调用,理解Rust和Wasm的交互过程。

使用Rust就需要做一些前置的环境配置,详情的步骤可参考Rust官网:https://www.rust-lang.org/zh-CN/tools/install。

安装wasm-pack,wasm-pack是一个构建、测试和发布Wasm的Rust CLI工具,我们将使用wasm-pack相关的命令来构建Wasm二进制内容。这有助于将代码编译为WebAssembly,并生成在浏览器中使用的正确包。

7.2Rust项目初始化

执行cargo new rust_wasm初始化Rust项目,自动生成配置文件Cargo.toml。

项目结构如下:

/Users/admin/RustroverProjects/rust_wasm

├── Cargo.lock

├── Cargo.toml

├── src

| └── lib.rs

└── target

├── CACHEDIR.TAG

└── debug

├── build

├── deps

├── examples

└── incremental

7.3配置包文件

我们可以在Cargo.toml文件中加上下列代码并保存,保存之后Cargo会自动下载依赖。

具体是:

  • 1)crate-type = ["cdylib"],表示编译时候使用C标准的动态库;
  • 2)#[wasm_bindgen]是一个属性宏,来自于wasm_bindgen这个crate,是一个简化Rust WASM与JS之间交互的库。

[lib]

crate-type = ["cdylib"]

[dependencies]

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

7.4编写代码

编写代码之前我们先明确Rust中crate包的概念,Rust中包管理系统将crate包分为二进制包(Binary)和库包(Library)两种,二者可以在同一个项目中同时存在。

二进制包:

  • 1)main.rs是二进制项目的入口;
  • 2)二进制项目可直接执行;
  • 3)一个项目中二进制包可以有多个,所以在Cargo.toml中通过双方括号标识 [[bin]]。

库包:

  • 1)lib.rs是库包的入口;
  • 2)库项目不可直接执行,通常用来作为一个模块被其他项目引用;
  • 3)一个项目中库包仅有1个,在Cargo.toml中通过单方括号标识 [lib]。

因为我们这里希望将 Wasm 转为一个可以在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

}

7.5执行编译

这里我们要使用到wasm-pack,将上述的Rust代码编译为能够被JS导入的模块,根据wasm-pack提供的target方式可以指定构建的产物。

如截图所示:

目录结构如下:

/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文件内容:

/* 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规范的模块,而且还支持自动生成d.ts文件,对模块的使用者非常友好。

如下:

7.6在前端项目中引入使用

'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代码计算结果:{addResult}

</div>

二分法方式{calculateTime}

</div>

</div>

</main>

);

}

7.7在IM场景下的性能比较

在IM场景下,聊天消息中核心的处理流程在于数据的排序、去重,大量的数据查找会非常耗时,在这里我们通过二分法的方式对Rust和JavaScript两种实现方式的耗时进行一个简单的对比。

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,

}

}

}

// 获取原始数据

fn get_origin_data(num: i32) -> Vec<Data> {

let mut data: Vec = vec![]; // 存储数据的向量

.... // 创建 num 个数据

data

}

// 初始化结构体数据

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!("插入{}条数据执行耗时:{}微秒", num, duration);

result

}

数据对比分析:

8、本文小结

在大多数场景下我们都不需要用到WebAssembly。因为V8等JS引擎的优化带来了巨大的性能提升,已经足够让JavaScript应对绝大多数的普通场景了,如果要做进一步优化密集计算任务时使用Web worker也都能解决掉。只有在以上的少数场景下,我们才需要做这种“二次提升”。

WebAssembly虽然有天然的优势,但也有自己的局限性,在使用时我们也需要考虑多方面因素,例如生态、开发成本等等。不过我们依然可以持续关注WebAssembly的发展。

9、相关资料

[1] 一文读懂前端技术演进:盘点Web前端20年的技术变迁史

[2] 新手入门贴:史上最全Web端即时通讯技术原理详解

[3] Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE

[4] 新手快速入门:WebSocket简明教程

[5] WebSocket详解(六):刨根问底WebSocket与Socket的关系

[6] WebSocket从入门到精通,半小时就够!

[7] 搞懂现代Web端即时通讯技术一文就够:WebSocket、socket.io、SSE

[8] 详解Web端通信方式的演进:从Ajax、JSONP 到 SSE、Websocket

[9] 从理论到实践,详细对比Electron和Tauri的优劣

[10] 快速对比跨平台框架Electron、Flutter、Tauri、React Native等

10、得物技术团队其它文章

得物从0到1自研客服IM系统的技术实践之路

得物自研客服IM中收发聊天消息背后的技术逻辑和思考实现

得物从零构建亿级消息推送系统的送达稳定性监控体系技术实践

得物基于Electron开发客服IM桌面端的技术实践

得物自研移动端弱网诊断工具的技术实践分享

得物移动端常见白屏问题优化(网络优化篇)

技术交流:

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK(备用地址点此)

(本文已同步发布于:http://www.52im.net/thread-4742-1-1.html)

相关推荐

JavaScript中常用数据类型,你知道几个?

本文首发自「慕课网」,想了解更多IT干货内容,程序员圈内热闻,欢迎关注!作者|慕课网精英讲师Lison这篇文章我们了解一下JavaScript中现有的八个数据类型,当然这并不是JavaScr...

踩坑:前端的z-index 之bug一二(zh1es前端)

IE6下浮动元素bug给IE6下的一个div设置元素样式,无论z-index设置多高都不起作用。这种情况发生的条件有三个:1.父标签position属性为relative;2.问题标签无posi...

两栏布局、左边定宽200px、右边自适应如何实现?

一、两栏布局(左定宽,右自动)1.float+margin即固定宽度元素设置float属性为left,自适应元素设置margin属性,margin-left应>=定宽元素宽度。举例:HTM...

前端代码需要这样优化才是一个标准的网站

  网站由前端和后端组成,前端呈现给用户。本文将告诉您前端页面代码的优化,当然仍然是基于seo优化的。  就前端而言,如果做伪静态处理,基本上是普通的html代码,正常情况下,这些页面内容是通过页面模...

网页设计如何自学(初学网页设计)

1在Dreamweaver中搭建不同的页面,需要掌握HTML的语句了,通过调整各项数值就可以制作出排版漂亮的页面,跟着就可以学习一些可视化设计软件。下面介绍网页设计如何自学,希望可以帮助到各位。Dre...

1、数值类型(数值类型有)

1.1数据类型概览MySQL的数据类型可划分为三大类别:数值类型:旨在存储数字(涵盖整型、浮点型、DECIMAL等)。字符串类型:主要用于存储文本(诸如CHAR、VARCHAR之类)。日期/...

网页设计的布局属性(网页设计的布局属性是什么)

布局属性是网站设计中必不可少的一个重要的环节,主要用来设置网页的元素的布局,主要有以下属性。1、float:该属性设置元素的浮动方式,可以取none,left和right等3个值,分别表示不浮动,浮在...

Grid网格布局一种更灵活、更强大的二维布局模型!

当涉及到网页布局时,display:flex;和display:grid;是两个常用的CSS属性,它们都允许创建不同类型的布局,但有着不同的用法和适用场景。使用flex布局的痛点当我们使...

React 项目实践——创建一个聊天机器人

作者:FredrikStrandOseberg转发链接:https://www.freecodecamp.org/news/how-to-build-a-chatbot-with-react/前言...

有趣的 CSS 数学函数(css公式)

前言之前一直在玩three.js,接触了很多数学函数,用它们创造过很多特效。于是我思考:能否在CSS中也用上这些数学函数,但发现CSS目前还没有,据说以后的新规范会纳入,估计也要等很久。然...

web开发之-前端css(5)(css前端设计)

显示控制一个元素的显示方式,我们可以使用display:block;display:inline-block;display:none;其中布局相关的还有两个很重要的属性:display:flex;和...

2024最新升级–前端内功修炼 5大主流布局系统进阶(分享)

获课:keyouit.xyz/14642/1.前端布局的重要性及发展历程前端布局是网页设计和开发的核心技能之一,它决定了页面元素如何组织和呈现。从早期的静态布局到现代的响应式布局,前端布局技术经历了...

教你轻松制作自动换行的CSS布局,轻松应对不同设备!

在网页设计中,自动换行的CSS布局是非常常见的需求,特别是在响应式设计中。它可以让网页内容自动适应不同屏幕尺寸,保证用户在不同设备上都能够获得良好的浏览体验。本文将介绍几种制作自动换行的CSS布局的方...

晨光微语!一道 CSS 面试题,伴你静享知识治愈时光

当第一缕阳光温柔地爬上窗台,窗外的鸟鸣声清脆悦耳,空气中弥漫着清新的气息。在这宁静美好的清晨与上午时光,泡一杯热气腾腾的咖啡,找一个舒适的角落坐下。前端的小伙伴们,先把工作的疲惫和面试的焦虑放在一边,...

2023 年的响应式设计指南(什么是响应式设计优缺点)

大家好,我是Echa。如今,当大家考虑构建流畅的布局时,没有再写固定宽度和高度数值了。相反,小编今天构建的布局需要适用于几乎任何尺寸的设备。是不是不可思议,小编仍然看到网站遵循自适应设计模式,其中它有...