注册
web

wasm真的比js快吗?

一. 前言


首先提一句话,本人是Rust新手!如果有什么不足的地方麻烦指出哈!


最近一直在玩Rust(摸鱼),本来是想着,多学一点,多练一练,之后把我们这边的一些可视化项目里面核心的代码用Rust重构一下。但是我最近在练习一个demo的时候,发现了跟我预期不一样的地方。


WechatIMG68186.png


具体如何,我用下面几个案例展开细说。


二. 案例1: 使用canvas绘制十万个不同颜色的圆


首先我想到的是,把canvas的复杂图像绘制功能用Rust重写一下。这里我用canvas绘制大量的圆形为例子。


2.1 Rust绘制canvas


跟上一篇文章的流程差不多,我们需要先新建一个Rust项目:


   cargo new canvas_circel_random --lib

然后更新一下Cargo.toml文件里面的依赖内容:


[package]
name = "canvas_circle_random"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
web-sys = { version = "0.3", features = ["HtmlCanvasElement", "CanvasRenderingContext2d"] }

完成之后,我们简单在src/lib.rs写一点代码:


// 引入相关的依赖
use wasm_bindgen::prelude::*;
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
use js_sys::Math;

// 给js调用的方法
#[wasm_bindgen]
pub fn draw_circles(canvas: HtmlCanvasElement) {
// 获取ctx绘画上下文
let context = canvas.get_context("2d").unwrap().unwrap().dyn_int0::<CanvasRenderingContext2d>().unwrap();
let width = canvas.client_width() as f64;
let height = canvas.client_height() as f64;

// 循环绘制
for _ in 0..100_0000 {
// 设置一下写x,y的位置
let x = Math::random() * width;
let y = Math::random() * height;
let radius = 10.0;
let color = format!(
"rgba({}, {}, {}, {})",
(Math::random() * 255.0) as u8,
(Math::random() * 255.0) as u8,
(Math::random() * 255.0) as u8,
Math::random()
);
draw_circle(&context, x, y, radius, &color);
}
}

fn draw_circle(context: &CanvasRenderingContext2d, x: f64, y: f64, radius: f64, color: &str) {
// 调用canvas的API绘制
context.begin_path();
context.arc(x, y, radius, 0.0, 2.0 * std::f64::consts::PI).unwrap();
context.set_fill_style(&JsValue::from_str(color));
context.fill();
context.stroke();```

}


简单解释一下代码:



  • 0..100_0000 创建了一个从 0 开始到 999,999 结束的范围注意,Rust 的范围是左闭右开的,这意味着它包含起始值但不包含结束值。
  • &JsValue::from_str(color)从变量中取值。

完成之后,我们去打包一下。


wasm-pack build --target web     

然后我们得到一个pkg包,如下图:


image.png


然后我们在项目中引入一下,具体流程可以看我上一篇文章。
回到我们的Vue项目中,我们引入一下:


import init, { draw_circles } from 'canvas_circle_random/canvas_circle_random'
onMounted(async () => {
await init();
const begin = new Date().getTime();
drawWasmCircle();
const end = new Date().getTime();
console.log('wasm cost time: ' + (end - begin) + 'ms');
})

之后我们打开一下页面:


image.png


多次加载了几次,加载范围大概在2750ms~2900ms之间。


2.2 使用js绘制canvas


const drawJsCircle = () => {
const canvas = document.getElementById('my-canvas') as HTMLCanvasElement;
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
for (let i = 0; i < 1000000; i++) {
drawRandomCircle(ctx, 800, 600);
}
}

const drawRandomCircle = (ctx: CanvasRenderingContext2D, width: number, height: number) => {
const radius = 10;
const x = Math.random() * (width - 2 * radius) + radius;
const y = Math.random() * (height - 2 * radius) + radius;
const color = `rgba(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.random().toFixed(2)})`;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
ctx.stroke();
}


没什么好说的,有手就会。


然后我们在页面上试一下:


image.png
加载范围大概在1950ms~2200ms之间。


卧槽,难道说js的性能比wasm快???


然后我又对绘制的数量和绘制的形状做了多次实验:



  • 绘制10000个圆, wasm用时大概在1000ms,js用时大概在700ms。
  • 绘制100000个长方形wasm用时大概在1700ms, js用时在1100ms。

无一例外,在绘制canvas上面,js的性能确实优于wasm


三. 案例2:算法性能


考虑到通过canvas绘制图形来判断性能,有点太前端化了,我想可不可以通过写一些算法来做一下性能的比较。
试了很多算法,这里我用一下斐波那契算法,比较简单也比较有代表性。


在同级目录下新建一个Rust项目:


    cargo new fb-lib --lib

然后在fb-lib中修改一下Cargo.toml


[package]
name = "fb-lib"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2"

把斐波那契数列代码写到src/lib.rs文件中:


use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn fb_wasm(n: i32) -> i32 {
if n <= 1 {
1
}
else {
fb_wasm(n - 1) + fb_wasm(n - 2)
}
}

很简单,没什么好说的。完成之后,我们在项目中使用一下。


<script setup lang="ts">
import init, { fb_wasm } from 'fb-lib/fb_lib'
import { onMounted } from 'vue';

onMounted(async () => {
await init();
const begin = new Date().getTime();
fb_wasm(42);
const end = new Date().getTime();
console.log('wasm cost time: ' + (end - begin) + 'ms');
})

</script>

image.png


大概试了一下时间在1700ms~1850ms左右。


然后我们用js实现一下:代码如下:


import init, { fb_wasm } from 'fb-lib/fb_lib'
import { onMounted } from 'vue';

onMounted(async () => {
await init();
const begin = new Date().getTime();
fn_js(42);
const end = new Date().getTime();
console.log('js cost time: ' + (end - begin) + 'ms');
})

const fn_js = (n: number): number => {
if (n <= 1) {
return 1;
} else {
return fn_js(n - 1) + fn_js(n - 2);
}
}

然后我们在页面上看一下:


image.png


大概试了一下时间在2550ms~2700ms左右。


很明显,这时的wasm的性能是要优秀于js


四. 总结


大概试了一下canvas,dom操作,高性能算法(排序、递归)等。我大概得出了一个这样的结论:



  • Wasm代码比JavaScript代码更加精简,因此从网络上获取Wasm代码的速度更快。
  • 对于一些高性能的算法,在基数足够大的情况下,wasm的性能确实高于js,但是当基数比较小的时候,两者其实差不多。
  • 由于Wasm是一种二进制格式,需要将DOM操作的数据进行转换,才能在Wasmjs之间进行传递。这个数据转换过程可能导致额外的开销。以及 Wasm操作DOM时,需要通过js提供的API进行通信。每次调用js的API都会引入一定的开销,从而影响性能。所以在一些页面交互操作上,wasm的性能并不会优于js

综上,个人觉得wasmjs之间是一种互相选择互相依靠的关系,并不是取代的关系。日常开发中,也要结合实际情况选择不同的方式进行开发。


往期文章:前端仔,用rust来写canvas


作者:尝尝你的优乐美
来源:juejin.cn/post/7444450769488674825

0 个评论

要回复文章请先登录注册