Wasm在即时通讯IM场景下的Web端应用性能提升初探
yuyutoo 2024-12-16 17:21 3 浏览 0 评论
本文由得物技术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)
相关推荐
- Mysql和Oracle实现序列自增(oracle创建序列的sql)
-
Mysql和Oracle实现序列自增/*ORACLE设置自增序列oracle本身不支持如mysql的AUTO_INCREMENT自增方式,我们可以用序列加触发器的形式实现,假如有一个表T_WORKM...
- 关于Oracle数据库12c 新特性总结(oracle数据库19c与12c)
-
概述今天主要简单介绍一下Oracle12c的一些新特性,仅供参考。参考:http://docs.oracle.com/database/121/NEWFT/chapter12102.htm#NEWFT...
- MySQL CREATE TABLE 简单设计模板交流
-
推荐用MySQL8.0(2018/4/19发布,开发者说同比5.7快2倍)或同类型以上版本....
- mysql学习9:创建数据库(mysql5.5创建数据库)
-
前言:我也是在学习过程中,不对的地方请谅解showdatabases;#查看数据库表createdatabasename...
- MySQL面试题-CREATE TABLE AS 与CREATE TABLE LIKE的区别
-
执行"CREATETABLE新表ASSELECT*FROM原表;"后,新表与原表的字段一致,但主键、索引不会复制到新表,会把原表的表记录复制到新表。...
- Nike Dunk High Volt 和 Bright Spruce 预计将于 12 月推出
-
在街上看到的PandaDunk的超载可能让一些球鞋迷们望而却步,但Dunk的浪潮仍然强劲,看不到尽头。我们看到的很多版本都是为女性和儿童制作的,这种新配色为后者引入了一种令人耳目一新的新选择,而...
- 美国多功能舰载雷达及美国海军舰载多功能雷达系统技术介绍
-
多功能雷达AN/SPY-1的特性和技术能力,该雷达已经在美国海军服役了30多年,其修改-AN/SPY-1A、AN/SPY-1B(V)、AN/SPY-1D、AN/SPY-1D(V),以及雷神...
- 汽车音响怎么玩,安装技术知识(汽车音响怎么玩,安装技术知识视频)
-
全面分析汽车音响使用或安装技术常识一:主机是大多数人最熟习的音响器材,有关主机的各种性能及规格,也是耳熟能详的事,以下是一些在使用或安装时,比较需要注意的事项:LOUDNESS:几年前的主机,此按...
- 【推荐】ProAc Response系列扬声器逐个看
-
有考牌(公认好声音)扬声器之称ProAcTablette小音箱,相信不少音响发烧友都曾经,或者现在依然持有,正当大家逐渐掌握Tablette的摆位设定与器材配搭之后,下一步就会考虑升级至表现更全...
- #本站首晒# 漂洋过海来看你 — BLACK&DECKER 百得 BDH2000L无绳吸尘器 开箱
-
作者:初吻给了烟sco混迹张大妈时日不短了,手没少剁。家里有了汪星人,吸尘器使用频率相当高,偶尔零星打扫用卧式的实在麻烦(汪星人:你这分明是找借口,我掉毛是满屋子都有,铲屎君都是用卧式满屋子吸的,你...
- 专题|一个品牌一件产品(英国篇)之Quested(罗杰之声)
-
Quested(罗杰之声)代表产品:Q212FS品牌介绍Quested(罗杰之声)是录音监听领域的传奇品牌,由英国录音师RogerQuested于1985年创立。在成立Quested之前,Roger...
- 常用半导体中英对照表(建议收藏)(半导体英文术语)
-
作为一个源自国外的技术,半导体产业涉及许多英文术语。加之从业者很多都有海外经历或习惯于用英文表达相关技术和工艺节点,这就导致许多英文术语翻译成中文后,仍有不少人照应不上或不知如何翻译。为此,我们整理了...
- Fyne Audio F502SP 2.5音路低音反射式落地音箱评测
-
FyneAudio的F500系列,有新成员了!不过,新成员不是新的款式,却是根据原有款式提出特别版。特别版产品在原有型号后标注了SP字样,意思是SpecialProduction。Fyne一共推出...
- 有哪些免费的内存数据库(In-Memory Database)
-
以下是一些常见的免费的内存数据库:1.Redis:Redis是一个开源的内存数据库,它支持多种数据结构,如字符串、哈希表、列表、集合和有序集合。Redis提供了快速的读写操作,并且支持持久化数据到磁...
- RazorSQL Mac版(SQL数据库查询工具)
-
RazorSQLMac特别版是一款看似简单实则功能非常出色的SQL数据库查询、编辑、浏览和管理工具。RazorSQLformac特别版可以帮你管理多个数据库,支持主流的30多种数据库,包括Ca...
你 发表评论:
欢迎- 一周热门
-
-
前端面试:iframe 的优缺点? iframe有那些缺点
-
带斜线的表头制作好了,如何填充内容?这几种方法你更喜欢哪个?
-
漫学笔记之PHP.ini常用的配置信息
-
其实模版网站在开发工作中很重要,推荐几个参考站给大家
-
推荐7个模板代码和其他游戏源码下载的网址
-
[干货] JAVA - JVM - 2 内存两分 [干货]+java+-+jvm+-+2+内存两分吗
-
正在学习使用python搭建自动化测试框架?这个系统包你可能会用到
-
织梦(Dedecms)建站教程 织梦建站详细步骤
-
【开源分享】2024PHP在线客服系统源码(搭建教程+终身使用)
-
2024PHP在线客服系统源码+完全开源 带详细搭建教程
-
- 最近发表
-
- Mysql和Oracle实现序列自增(oracle创建序列的sql)
- 关于Oracle数据库12c 新特性总结(oracle数据库19c与12c)
- MySQL CREATE TABLE 简单设计模板交流
- mysql学习9:创建数据库(mysql5.5创建数据库)
- MySQL面试题-CREATE TABLE AS 与CREATE TABLE LIKE的区别
- Nike Dunk High Volt 和 Bright Spruce 预计将于 12 月推出
- 美国多功能舰载雷达及美国海军舰载多功能雷达系统技术介绍
- 汽车音响怎么玩,安装技术知识(汽车音响怎么玩,安装技术知识视频)
- 【推荐】ProAc Response系列扬声器逐个看
- #本站首晒# 漂洋过海来看你 — BLACK&DECKER 百得 BDH2000L无绳吸尘器 开箱
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)