一. 前言
首先提一句话,本人是Rust
新手!如果有什么不足的地方麻烦指出哈!
最近一直在玩Rust
(摸鱼),本来是想着,多学一点,多练一练,之后把我们这边的一些可视化项目里面核心的代码用Rust
重构一下。但是我最近在练习一个demo的时候,发现了跟我预期不一样的地方。
具体如何,我用下面几个案例展开细说。
二. 案例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_into::<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包,如下图:
然后我们在项目中引入一下,具体流程可以看我上一篇文章。 回到我们的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');
})
之后我们打开一下页面:
多次加载了几次,加载范围大概在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();
}
没什么好说的,有手就会。
然后我们在页面上试一下:
卧槽,难道说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>
大概试了一下时间在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);
}
}
然后我们在页面上看一下:
大概试了一下时间在2550ms~2700ms
左右。
很明显,这时的wasm
的性能是要优秀于js
。
四. 总结
大概试了一下canvas
,dom操作
,高性能算法(排序、递归)
等。我大概得出了一个这样的结论:
Wasm代码比JavaScript代码更加精简
,因此从网络上获取Wasm代码的速度更快。- 对于一些高性能的算法,在基数足够大的情况下,
wasm
的性能确实高于js
,但是当基数比较小的时候,两者其实差不多。 - 由于Wasm是一种二进制格式,需要将
DOM操作的数据进行转换
,才能在Wasm
和js
之间进行传递。这个数据转换过程可能导致额外的开销
。以及Wasm操作DOM时
,需要通过js
提供的API进行通信。每次调用js
的API都会引入一定的开销,从而影响性能。所以在一些页面交互操作上,wasm
的性能并不会优于js
。
综上,个人觉得wasm
和js
之间是一种互相选择互相依靠的关系,并不是取代的关系。日常开发中,也要结合实际情况选择不同的方式进行开发。
——转自作者:尝尝你的优乐美
出处:juejin.cn/post/7444450769488674825