wasm真的比js快吗?

前端RustJavaScript

一. 前言

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

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

picture.image

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

二. 案例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包,如下图:

picture.image

然后我们在项目中引入一下,具体流程可以看我上一篇文章。 回到我们的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');
})

之后我们打开一下页面:

picture.image

多次加载了几次,加载范围大概在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();
   }

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

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

picture.image

卧槽,难道说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>

picture.image

大概试了一下时间在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);
  }
}

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

picture.image

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

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

四. 总结

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

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

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

——转自作者:尝尝你的优乐美 

出处:juejin.cn/post/7444450769488674825

0
0
0
0
关于作者
相关资源
融合开放,新一代边缘云网络平台 | 第 11 期边缘云主题Meetup
《融合开放,新一代边缘云网络平台 》李冰|火山引擎边缘云网络产品负责人
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论