当前位置:首页 > 科技  > 软件

宝贝,带上WebAssembly,换个姿势来优化你的前端应用

来源: 责编: 时间:2024-06-07 17:19:25 218观看
导读前言说起,「前端性能优化」,大家可能第一时间就会从网络/资源加载/压缩资源等角度考虑。正如下面所展示的一样。图片图片图片上面所列的措施,是我们常规优化方案。针对上面的内容我们有机会来讲讲该如何做。而今天呢,我们

前言

说起,「前端性能优化」,大家可能第一时间就会从网络/资源加载/压缩资源等角度考虑。W8i28资讯网——每日最新资讯28at.com

正如下面所展示的一样。W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

上面所列的措施,是我们常规优化方案。针对上面的内容我们有机会来讲讲该如何做。W8i28资讯网——每日最新资讯28at.com

而今天呢,我们和大家唠唠利用WebAssembly来优化前端渲染链路或者针对关键节点进行调优处理。W8i28资讯网——每日最新资讯28at.com

我们能所学到的知识点

  1. WebAssembly是个啥?
  2. 项目初始化&配置
  3. Rust项目初始化
  4. 处理耗时任务
  5. 图像处理
  6. 优化音视频
  7. 优化游戏体验

1. WebAssembly是个啥?

WebAssembly是一种「二进制指令格式」,旨在在浏览器中高效执行。W8i28资讯网——每日最新资讯28at.com

  • 它「作为JavaScript的补充」,允许我们用Rust、C++和C等语言编写性能关键代码,并在浏览器中运行。
  • 通过将代码编译成Wasm,它变得「平台无关」,并且可以以接近本地的速度运行。
  • Rust是一种以安全性和性能著称的系统编程语言,由于其强大的保证和与Wasm的无缝集成,已经在WebAssembly生态系统中获得了广泛的关注。WebAssembly为网络开发开辟了新的可能性,在一些复杂任务如游戏引擎、图像处理等方面有着显著的性能提升。

WebAssembly 的优势

WebAssembly的一个最具说服力的特点是其在「计算密集型任务」中的性能提升。例如,在对庞大数据集进行复杂的统计计算时,WebAssembly 可能比常规的 JavaScript 快得多。这是因为 WebAssembly 的高度优化设计使得代码执行速度远远超过 JavaScript。W8i28资讯网——每日最新资讯28at.com

WebAssembly 的另一个优点是其「可移植性」。跨平台应用程序的开发变得非常简单,因为可以从多种语言生成 WebAssembly 代码,并在任何平台上执行。W8i28资讯网——每日最新资讯28at.com

最后,「安全性」也是 WebAssembly 架构中的一个重要考虑因素。由于 WebAssembly 提供了沙箱执行环境,代码无法访问敏感数据或运行恶意代码。W8i28资讯网——每日最新资讯28at.com

下面是了解和学习WebAssembly的RoadMap。W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

2. 项目初始化&配置

进入正题之前,我们还是和之前一样,使用我们自己的脚手架-f_cli_f[1]构建一个以Vite为打包工具的前端项目。W8i28资讯网——每日最新资讯28at.com

在本地合适的目录下执行如下代码:W8i28资讯网——每日最新资讯28at.com

npx f_cli_f create wasm_preformance

然后,我们在pages中新建如下的目录结构:W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

其中wasm存放的是我们已经构建好的wasm的资源。W8i28资讯网——每日最新资讯28at.com

配置Web Worker

由于我们在项目中会用到Web Worker,所以我们还需要对其做一定的配置。W8i28资讯网——每日最新资讯28at.com

而今天,我们再介绍另外一种更加优雅的方式 - Comlink[2]。W8i28资讯网——每日最新资讯28at.com

Comlink是一个由Google Chrome Labs开发的轻量级库,它旨在简化Web Worker与主线程之间的通信,让我们能够充分利用多线程处理的威力,提升前端应用性能。W8i28资讯网——每日最新资讯28at.com

由于,我们是用Vite搭建的前端项目,所以我们还需要在项目中借助vite-plugin-comlink[3]。W8i28资讯网——每日最新资讯28at.com

我们可以通过如下代码安装对应的依赖。W8i28资讯网——每日最新资讯28at.com

yarn add -D vite-plugin-comlinkyarn add comlink

然后,将对应的库配置到vite.config.js中。W8i28资讯网——每日最新资讯28at.com

import { comlink } from "vite-plugin-comlink";export default {  plugins: [comlink()],  worker: {    plugins: () => [comlink()],  },};

这里有一点需要额外注意,comlink要放置在plugins第一个位置。W8i28资讯网——每日最新资讯28at.com

针对TypeScript项目,我们还需要在vite-env.d.ts中新增/// <reference types="vite-plugin-comlink/client" />。W8i28资讯网——每日最新资讯28at.com

然后我们就可以用优雅的方式来使用WebWorker了。W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

可以看到,使用了comlink后,我们在使用多线程能力时,不需要写那么多模板代码,而是通过Promise来接收从子线程返回的数据。W8i28资讯网——每日最新资讯28at.com

配置WebAssembly

如果看过我们之前的文章(Rust 赋能前端 -- 写一个 File 转 Img 的功能)就对这块不会陌生。W8i28资讯网——每日最新资讯28at.com

在Vite项目中使用WebAssembly我们需要配置vite-plugin-wasm[4]和vite-plugin-top-level-await[5]W8i28资讯网——每日最新资讯28at.com

然后,也是需要在vite.config.js的plugin和worker中进行相关处理。这里就不展开说明了。之前的文章有过解释。W8i28资讯网——每日最新资讯28at.com

3. Rust项目初始化

在讲项目页面结构时说过,我们在组件目录中特意有一个wasm目录用于存放编译好的wasm信息。W8i28资讯网——每日最新资讯28at.com

我们选择wasm代码和前端项目分离的方式,也就是我们会重新启动一个Rust项目。W8i28资讯网——每日最新资讯28at.com

通过如下代码在合适的文件目录下执行。W8i28资讯网——每日最新资讯28at.com

cargo new --lib rust_comformation2web

然后,因为我们想要把Rust编译成wasm并且还需要操作对应的dom等。所以,我们需要按照对应的crate。W8i28资讯网——每日最新资讯28at.com

安装依赖

所以,我们来更新对应的Cargo.toml。W8i28资讯网——每日最新资讯28at.com

[package]name = "rust_comformation2web"version = "0.1.0"edition = "2021"[lib]crate-type = ["cdylib"][dependencies]wasm-bindgen = "0.2.92"console_error_panic_hook = "0.1.7"js-sys = "0.3.69"[dependencies.web-sys]version = "0.3.69"features = [    'Document',    'TextMetrics',    'CanvasRenderingContext2d',    'HtmlCanvasElement',    'Window']

然后,我们就可以在src/lib.rs写我们对应的代码了。W8i28资讯网——每日最新资讯28at.com

如果对自己的代码质量不是很放心,并且又不想写Test模块了,我们将Rust所在的文件目录,构建成一个Node项目(通过npm init),并配合对应的打包软件(Webpack)来直接验证wasm的效果。W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

对应的webpack.config.js的配置如下:W8i28资讯网——每日最新资讯28at.com

const path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin');const webpack = require('webpack');const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");module.exports = {    entry: './index.js',    output: {        path: path.resolve(__dirname, 'dist'),        filename: 'index.js',    },    plugins: [         new HtmlWebpackPlugin({            template: 'index.html'        }),        new WasmPackPlugin({            crateDirectory: path.resolve(__dirname, ".")        }),        // 让这个示例在不包含`TextEncoder`或`TextDecoder`的Edge浏览器中正常工作。        new webpack.ProvidePlugin({          TextDecoder: ['text-encoding', 'TextDecoder'],          TextEncoder: ['text-encoding', 'TextEncoder']        })    ],    mode: 'development',    experiments: {        asyncWebAssembly: true   }};

然后,我们在package.json新增两个命令W8i28资讯网——每日最新资讯28at.com

"scripts": {    "build": "webpack",    "serve": "webpack serve"  },

我们就可以通过yarn serve查看效果亦或者yarn build执行对应的rust打包。W8i28资讯网——每日最新资讯28at.com

能够实现这一切的功劳都是-@wasm-tool/wasm-pack-plugin[6]所赐予的。W8i28资讯网——每日最新资讯28at.com

编译处理

但是呢,我们对Rust编译处理不使用之前的yarn build,而是使用cargo自己的构建工具 - wasm-pack[7]。W8i28资讯网——每日最新资讯28at.com

wasm-pack build --target web --release

如果一切都正常的话,对应的wasm就会被打包到pkg文件夹下面了。W8i28资讯网——每日最新资讯28at.com

然后,我们就可以将所有文件复制到Vite项目中的wasm/xx目录下。W8i28资讯网——每日最新资讯28at.com

最后,我们就可以在React组件中通过。W8i28资讯网——每日最新资讯28at.com

import init, { fib } from './wasm/xx';

引入对应的wasm函数了。W8i28资讯网——每日最新资讯28at.com

前面铺垫了那么多,其实为了更好的讲下面的内容,我们先把一些和逻辑代码不相关的配置内容提前介绍了,这样我们就可以将更过的注意力放在代码实现上了。W8i28资讯网——每日最新资讯28at.com

4. 处理耗时任务

先说结果

图片图片W8i28资讯网——每日最新资讯28at.com

当执行一个处理耗时任务时,WebAssembly/JS WebWorker/JS主线程三者的执行时间是由低到高排列的。W8i28资讯网——每日最新资讯28at.com

WebAssembly < JS WebWorker<JS主线程。W8i28资讯网——每日最新资讯28at.com

针对上面的我们有几点需要注意:W8i28资讯网——每日最新资讯28at.com

  1. JS WebWorker针对JS主线程优化率不是很高,(有时候worker执行时间甚至比JS主线程长)。
  2. WebAssembly通过至极的内存优化,还可以将优化率提高到50%以上。

听我解释

我们都知道JS是单进程的,所以我们在处理一些处理耗时任务就会很吃力。当然,我们也可以借助Web Worker来开启新的子线程来缓解主线程的计算压力。但是,在一些计算量特别大的功能面前,一切的计算都是收效甚微的。W8i28资讯网——每日最新资讯28at.com

其实,将一些处理耗时任务放置到Web Worker中只是不想让耗时任务过多的占用主线程资源,从而让页面没有卡顿的感觉。这就是大家所熟悉的浏览器在 1 秒钟内完成 60 次图像的绘制,用户才会感觉页面顺畅。W8i28资讯网——每日最新资讯28at.com

为了在前端环境模拟处理耗时任务,我们采用在前端环境中执行一个fibonacci的计算过程。W8i28资讯网——每日最新资讯28at.com

在WasmPerformance的index.tsx中有如下的页面操作。W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

也就是说,我们在JS主线程/JS WebWorker/WebAssembly中分别执行一个耗时的fibonacci。W8i28资讯网——每日最新资讯28at.com

我们在tool.ts中构建了一个最简单的fibonacci函数。W8i28资讯网——每日最新资讯28at.com

function fibJS(n: number): number {  if (n < 2) {    return n;  }  return fibJS(n - 1) + fibJS(n - 2);}

对应的页面代码如下:W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

从上面我们看到几个关键的点:W8i28资讯网——每日最新资讯28at.com

我们用state来维护计算的结果和时间。W8i28资讯网——每日最新资讯28at.com

const [calculateInfo, setCalculateInfo] = useState<CalculateInfo>({    js: { result: 0, executionTime: 0 },    wasm: { result: 0, executionTime: 0 },    webworker: { result: 0, executionTime: 0 },  });

然后,我们在handleCalculate中执行不同的操作逻辑。W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

其中measureExecutionTime是我们在tool定义的用于检测指定函数被执行时的所用时间的函数。W8i28资讯网——每日最新资讯28at.com

function measureExecutionTime<T extends (...args: any[]) => any>(  fn: T): (...args: Parameters<T>) => { result: ReturnType<T>; executionTime: number } {  return function (...args: Parameters<T>): { result: ReturnType<T>; executionTime: number } {    const start = performance.now();    const result = fn.apply(this, args);    const end = performance.now();    const executionTime = end - start;    return { result, executionTime };  };}

还有,我们在handleCalculate在接收到type为3时,是触发了一个wasm版本的fibonacci函数。W8i28资讯网——每日最新资讯28at.com

由于,对应的Rust代码如下:W8i28资讯网——每日最新资讯28at.com

use wasm_bindgen::prelude::*;#[wasm_bindgen]pub fn fib(n: usize) -> usize {    match n {        0 => 0,        1 => 1,        _ => fib(n - 1) + fib(n - 2),    }}

而上面的Rust代码会通过wasm-pack build --target web --release进行打包处理,并且打包后的相关内容被复制到了前端项目中wasm/calculate。W8i28资讯网——每日最新资讯28at.com

然后在组件中通过import init, { fib } from './wasm/calculate';方式来导入。W8i28资讯网——每日最新资讯28at.com

5. 图像处理

先说结果

图片图片W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

我们写了两个示例:W8i28资讯网——每日最新资讯28at.com

  1. 将指定文本信息绘制到图片上
  2. 将特定图形绘制到图片上

无论是哪种情况,我们可以得出一个比较明显的情况。W8i28资讯网——每日最新资讯28at.com

在图像处理的部分功能点上,WebAssembly的性能远高于JS。W8i28资讯网——每日最新资讯28at.com

因为,我们这里没做WebAssembly的内存优化,当处理数据「超级大」时,由于数据传输的问题,反而WebAssembly的执行时间会比JS长。但是呢,这块不在我们的讨论范围内。后期有机会写相关的文章。W8i28资讯网——每日最新资讯28at.com

下面,我们就按照上面的示例来分别讲讲它们的代码实现。有些代码的逻辑其实很简单,我们已经有对应的注释,所以也不会用多余的篇幅解释。W8i28资讯网——每日最新资讯28at.com

绘制文本到图片上

对应的页面结构如下:W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

我们还是用了一个state来维护状态信息。W8i28资讯网——每日最新资讯28at.com

const [drawInfo, setDrawInfo] = useState<DrawInfo>({    js: { url: '', executionTime: 0 },    wasm: { url: '', executionTime: 0 },    js_circle: { url: '', executionTime: 0 },    wasm_circle: { url: '', executionTime: 0 },  });

然后在handleDraw中处理事件逻辑。W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

其中drawTextToCanvas是利用JS来绘制文本到Canvas,而drawTextToCanvasWasm是利用wasm处理相关逻辑。W8i28资讯网——每日最新资讯28at.com

JS 版本的drawText

图片图片W8i28资讯网——每日最新资讯28at.com

该函数定义在tool.ts中,然后就是接收一个String类型的数据,并将其渲染到Canvas中。W8i28资讯网——每日最新资讯28at.com

Rust 版本的drawText

图片图片W8i28资讯网——每日最新资讯28at.com

然后,别忘记在头部引入对应的crate。W8i28资讯网——每日最新资讯28at.com

use wasm_bindgen::prelude::*;use wasm_bindgen::JsCast;use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};extern crate console_error_panic_hook;use std::panic;

其实这块的逻辑,和之前我们讲的Rust 赋能前端 -- 写一个 File 转 Img 的功能的核心功能是类似的。W8i28资讯网——每日最新资讯28at.com

该函数通过wasm-pack编译到pkg中,然后我们复制对应的文件到React项目的wasm/draw中。W8i28资讯网——每日最新资讯28at.com

然后我们通过如下代码:W8i28资讯网——每日最新资讯28at.com

import init4Draw, {  draw_text_to_canvas as drawTextToCanvasWasm,  draw_circle_to_canvas as drawCircleToCanvasWasm,} from './wasm/draw';

进行函数的导入。W8i28资讯网——每日最新资讯28at.com

绘制图形到图片上

对应的页面结构和事件回调和之前是类似的,我们就省略了这部分的解释。W8i28资讯网——每日最新资讯28at.com

JS 版本的drawCircle

该部分也是定义在tool.ts中:W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

Rust 版本的drawCircle

图片W8i28资讯网——每日最新资讯28at.com

此函数的处理过程和drawText是一样的。W8i28资讯网——每日最新资讯28at.com

利用Photon操作图形

针对图片操作,不单单只有绘制文本/绘制图案,其实我们还可以做类似(裁剪/新增水印/图片翻转等)。W8i28资讯网——每日最新资讯28at.com

我们可以借助一些成熟的WebAssembly来做上述的操作。这里呢,给大家推荐一个库Photon[9]。W8i28资讯网——每日最新资讯28at.com

Photon 是一个高性能的图像处理库,用 Rust 编写并可编译为 WebAssembly,既可以在本地使用 Web 也可以在 Web 上使用。W8i28资讯网——每日最新资讯28at.com

这是它能做相关功能:W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

6. 优化音视频

写到这里呢,我们就不在罗列相关代码了。所以,我们给出一些针对音视频的优化的解决方案。W8i28资讯网——每日最新资讯28at.com

在这里我们介绍一种wasm库-ffmpeg.wasm[10]。W8i28资讯网——每日最新资讯28at.com

ffmpeg.wasm 是 FFmpeg[11] 的针对 WebAssembly / JavaScript 端口,支持在浏览器中录制、转换和流式传输视频和音频。它利用 Emscripten 来转译 FFmpeg 源代码和许多库得到。W8i28资讯网——每日最新资讯28at.com

具体的功能和库如下:W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

7. 优化游戏体验

得益于WebAssembly极致的内存管理,然后其二进制特性,WebAssembly 提供接近本地执行速度的性能,使得复杂的游戏逻辑和高帧率的图形渲染可以在浏览器中高效运行。W8i28资讯网——每日最新资讯28at.com

还得之前我们写过Game = Rust + WebAssembly + 浏览器。W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

还有,如果我们想要更多的效果,我们可以选择使用bevy[12] - 一款基于Rust的数据驱动的游戏引擎。W8i28资讯网——每日最新资讯28at.com

然后我们还在itch.io[13]查看哪些游戏是用Rust写的。W8i28资讯网——每日最新资讯28at.com

图片图片W8i28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-92743-0.html宝贝,带上WebAssembly,换个姿势来优化你的前端应用

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 从入门到精通:Python OpenPyXL完整教程

下一篇: Oxlint 会取代 Eslint 吗?

标签:
  • 热门焦点
Top