给 Web 前端工程师看的用 Rust 开发 wasm 组件实战

这篇具有很好参考价值的文章主要介绍了给 Web 前端工程师看的用 Rust 开发 wasm 组件实战。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

什么是wasm组件?

wasm 全称 WebAssembly,是通过虚拟机的方式,可以在服务端、客户端如浏览器等环境执行的二进制程序。他有速度快、效率高、可移植的特点。

对我们 Web 前端工程最大的好处就是可以在浏览器端使用二进制程序处理一些计算量大的处理,使用他比 javascript 快的特点优化性能。

目前浏览器对wasm的兼容性如下:

在移动端除了 android 4.4 和 ios 10 下不支持外,其他版本都能提供支持。还需要注意的是 wasm 有可能占用大量内存,使用第三方包含 wasm 调用的组件需要注意内存占用防止闪退。

为什么用Rust?

wasm模块 可以用多种语言来编译,包括 C/C++/C#、Rust、JAVA、Go。在这里使用 Rust 是因为他有严格的内存管理机制,从语法上尽量避免内存溢出,让工程师写出更安全的程序。

而且还有配套的工具 wasm-pack,让使用 Rust 编写的代码,编译包装成 npm 包,让使用这段程序的其他代码可以像使用其他公共库一样调用,不需要额外学习成本。

工具安装

  1. 安装 rustup,他是 Rust 安装器和版本管理工具。对于 web 前端来说相当于 nvm 这样的工具。
    按照 rust 官网的方法安装:https://www.rust-lang.org/zh-CN/tools/install
    同时也会安装 cargo,他是 Rust 的构建工具和包管理器。对于 web 前端来说相当于 npm 这样的工具。

  2. 安装 wasm-pack,他是上文提到的把 rust 程序编译包装成 wasm 组件的工具。
    同样按照 wasm-pack 官网的方法安装:https://rustwasm.github.io/wasm-pack/installer/

  3. 使用 wasm 模板
    使用 wasm-pack 提供的模板可以快速生成 rust 的 wasm 项目。

cargo generate --git https://github.com/rustwasm/wasm-pack-template


输入希望的项目目录名称,将新建目录并在其中生成项目。

在目录下我们可以看到几个文件,其中一个是 Cargo.toml ,这个是 rust 项目的描述文件,对于 web 前端来说相当于 package.json 文件。

项目目录下还有一个 src 目录,里面有 lib.rs 和 utils.rs 两个文件,其中 lib.rs 这个文件就是我们主要的逻辑入口,他引用了 wasm-bindgen 库来输出暴露给外部调用的接口,在函数之前加上#[wasm_bindgen]可以让外部调用这个方法。

编译项目

本来 rust 的项目编译用的是 cargo build 的命令,但是我们这里是希望编译 wasm 组件,所以用的是 wasm-pack build 命令。

执行后会在项目目录下的 pkg 目录下生成编译后的产品,是一个 npm 包的结构。需要调用这个组件的逻辑只需要像其他公共包一样 import 就可以使用了。

实战

以上的就是 wasm-pack 官方的教程,还有其他组件测试、发布等的流程先不在这里介绍了。以下用一个实际开发中的模块来说一下开发 wasm 组件过程中遇到的问题和解决方法。

背景

需要使用的 wasm 组件是一个优化3D模型的方法,传入一个模型的顶点信息和距离阈值,比较每个顶点位置之间的距离,如果没达到阈值距离就合并这两个顶点,以达到减少顶点的优化目的。

原逻辑是使用 javascript 编写的,在模型顶点数量比较多的时候执行的时间比较长。这种大量计算的情况就很适合使用 wasm 来处理。

数据传递

顶点信息是存储在一个 Float32Array 的数组中的,而 wasm 设计上除了 int 和 float 类型(对应 javascript 就是 number 类型)可以直接传递外,其他的类型都通过地址来传递。这对我们的程序来说是好消息,因为顶点信息的数据非常多,如果以值传递,就需要做数据复制,这个过程消耗的时间可能比我们换成 wasm 处理减
少的时间还要多。得益这个特点,我们的入参可以直接传入。

/*---  rust ----*/

// rust 获取 javasctipt 数据
pub fn add_attribute(&mut self, attribute: &Float32Array, item_size: u32) {
    self.attributes.push(BufferAttribute {
        array: attribute.to_vec(),
        item_size,
    });
}


/*---  javascript ----*/

// javascript 传递数据到 rust
for (const name of attributeNames) {
  const attr = attrArrays[name]
  bg.add_attribute(attr.array, attr.itemSize)
}


而计算后的结果,wasm 也提供了返回数组的指针和数组长度的方法,javascript 可以读取 wasm 的内存空间,根据这两个值构造新的顶点信息Float32Array。

/*---  rust ----*/

// 返回指定数据的内存指针位置
pub fn get_attribute_ptr(&self, index: usize) -> *const f32 {
  self.attributes[index].array.as_ptr()
}

// 返回指定数据的长度
pub fn get_attribute_length(&self, index: usize) -> usize {
  self.attributes[index].array.len()
}


/*---  javascript ----*/

// javascript 或取 rust 内存空间中的指定部分,构建Float32Array
const ptr = bg.get_attribute_ptr(i)
const length = bg.get_attribute_length(i)

const buffer = new attr.array.constructor(wasm.getMemory().buffer, ptr, length)


数据类型

合并顶点计算的逻辑中,有一段是这样的:每个顶点的位置、UV等信息,经过给定的精度计算后,生成一个特征值,之后比较每个顶点的特征值,如果是相同的话就表示这两个顶点可以合并。

原 javascript 版本的代码是逐个信息按顺序,加上分隔号,拼成一个字符串。

rust 版本的代码如果也按同样的方法处理,因为顶点的信息量是不定的,有可能只有位置信息,也有可能有UV、法线、颜色等信息,所以生成的特征值字符串长度也不确定。

rust 对於可变长度的字符串使用 String 类型,每次对字符串使用push_str方法增加内容。得到的结果 wasm 版本的执行速度跟 javascript 版本相差不大,甚至在某些情况下耗时还更多,经过逐个过程作排查,发现是在生成特征值和在表中查询特征值这个过程中花费的时间比较多。

根据程序的意图,特征值并不一定要是字符串,只需要在不同输入值的时候能够输出相关的值就可以,这跟生成 hash 值的需求是一样的,于是考虑将特征值生成替换成 hash 值计算。

因为在存储特征值的表使用了std::collections::hash_map类型,于是 hash 值也使用了其下的std::collections::hash_map::DefaultHasher类来计算

use std::collections::hash_map::DefaultHasher;

...

let mut hasher = DefaultHasher::new();

for j in 0..self.attributes.len() {
  ...

  let value = (attr.array[i * attr.item_size as usize + index as usize]
    * self.shift_multiplier)
    .trunc() as i32;
    
  hasher.write_i32(value);
    
  ...
}

let hash = hasher.finish();


需要注意的是对写入不同类型的内容,需要调用不同的方法,顶点信息中的值是正负值都用,经过精度计算后取整得到的值类型是i32,所以用write_i32来写入内容。

生成的 hash 值为u64,作为hash_mapkey记录对应顶点的序号。

替换特征值的类型之后,wasm 版本的耗时达到了 javascript 版本的 1/2,基本符合 wasm 设计的性能范围。

适配打包工具

wasm-pack 工具打包出来的 npm 包,可以直接在webpack下加载并调用运行。

我们原本的项目使用 vite 构建,vite 对import wasm 组件策略和 webpack 的不一样,vite 加载会返回一个加载方法,调用加载方法会返回一个 Promise,resolve 后才会返回跟 webpack 加载一样的 wasm 组件。

我们要对 wasm-pack 生成的产物作一些修改,假设我们的 wasm 组件命名为 merge_vertice_wasm,生成的主 js 文件应该会命名为merge_vertice_wasm.js,内容如下:

import * as wasm from './merge_vertice_wasm_bg.wasm'
import { __wbg_set_wasm } as wasm_bg from './merge_vertice_wasm_bg.js'
__wbg_set_wasm(wasm);
export * from './merge_vertice_wasm_bg.js'


为兼容 vite 的加载策略,修改成下面的内容

import * as wasm from './merge_vertice_wasm_bg.wasm'
import * as wasm_bg from './merge_vertice_wasm_bg.js'

let memory
if (wasm.default) {
  wasm.default({
    './merge_vertice_wasm_bg.js': wasm_bg,
  }).then(_wasm => {
    memory = _wasm.memory
    wasm_bg.__wbg_set_wasm(_wasm)
  })
} else {
  memory = _wasm.memory
  wasm_bg.__wbg_set_wasm(wasm)
}
export * from './merge_vertice_wasm_bg.js'

export function getMemory() {
  return memory
}


就可以在 webpack 和 vite 下都可以顺利加载并运行了。

其中增加了getMemory的方法供外部获取 wasm 组件的内存空间。

wasm调用javascript方法

当我们在调试和测试性能表现时,需要打印日志,由于我们的 wasm 跑在浏览器环境中,我们需要调用 javascript 的方法,比如console.logconsole.time

wasm-bindgen 库提供了 web-sys 的组件,让 rust 可以调用这些方法。

首先需要在cargo.toml中添加 web-sys 的依赖,并声明需要用到的特性:

[dependencies]
wasm-bindgen = "0.2.84"

[dependencies.web-sys]
version = "0.3.64"
features = ["console"]


这样在下次编译的时候,cargo 就会自动处理这些依赖,将会下载并构建。

然后在我们的 rust 文件中,加入对 web-sys 的引用:

extern crate web_sys;


就可以调用 javascript 的 console 下的方法了:

// 调用console.log
web_sys::console::log_1(&JsValue::from(logContent));

// 调用console.time(label)
web_sys::console::time_with_label(label);

// 调用console.timeEnd(label)
web_sys::console::time_end_with_label(label);


原 javascript 版本优化模型耗时:

wasm 版本优化模型耗时:

总结

以上为根据官网文档把模型合并顶点优化方法迁移为 wasm 版本的开发经历,从安装工具到发布、调试的整个过程。

中间因为对 rust 数据类型的不熟悉和对不同前端构建工具对 wasm 组件处理的不同不够清晰,在开发过程中遇到的问题和解决方法。

rust 版本的代码逻辑基本上是从 javascript 版本翻译过来的,其中应该还有在 rust 环境下的优化手段,将在之后的学习中继续迭代。

以上的完整代码在http://xingyun.jd.com/codingRoot/LemonTea-lib/merge-vertice-wasm/仓库,欢迎提优化修改建议。

引用

rust 官方文档:https://doc.rust-lang.org/book/

rust wasm 官方介绍文档:https://rustwasm.github.io/docs/book/

wasm-pack 官方文档:https://rustwasm.github.io/docs/wasm-pack/

作者:京东零售 胡俊文

来源:京东云开发者社区 转载请注明来源文章来源地址https://www.toymoban.com/news/detail-748258.html

到了这里,关于给 Web 前端工程师看的用 Rust 开发 wasm 组件实战的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • uniapp打包微信小程序详细步骤【前端开发】,web前端开发工程师

    II.打包发布 1️⃣ 获取微信小程序id: 首先,登录微信公众平台,查看自己 微信小程序的id号并复制 : 2️⃣ 填写自己的小程序id: 打开uniapp自己的工程文件,选择 manifest.json文件-微信小程序配置 ,黏贴自己的小程序id: 3️⃣ 发布: 选中 发行-小程序-微信 之后出现下面的

    2024年04月27日
    浏览(45)
  • 初级web前端开发工程师的岗位职责描述(合集)

      初级web前端开发工程师的岗位职责描述1 职责: 1. 根据功能需求设计编写页面原型; 2. 前后端联调保证功能流畅; 3. 提高页面易用性、美观提出合理建议。 4、与后台工程师配合开发联调并交付产品; 5、持续优化前端页面体验和访问速度,保证页面精美高效。 任职要求 1、

    2024年02月14日
    浏览(45)
  • 程序员在职场中如何让自己的技能快速提高,WEB前端开发工程师如何让自己快速成为团队的核心开发人员?

    今天聊一聊 就是我们在工作中就实际的 工资里面 写代码的过程中怎么提高自己的 一些技能 以及我们怎么样快速成为 就是一个团队的核心开发人 包括我以前 其实我也是一个小迷弟 后来就是慢慢慢慢 包括经历一些项目什么东西 其实现在已经慢慢成为核心开发人员 就是分享

    2023年04月16日
    浏览(64)
  • 前端开发工程师

        前端开发工程师,又叫web前端开发工和师,在国外早就有此职业,我国在2000年后开始兴起互联网,也就是在此时段开始,我国 慢慢的就有了前端开发这个职业,慢发展进程持续到2013年2014年以后,前端开发工程师这个职业最终被重视,于是市场上便逐渐大量出现各类IT职

    2024年02月01日
    浏览(45)
  • 成为一名高级前端开发工程师

    前后端面试题库 (面试必备) 推荐:★★★★★ 地址:前端面试题库  web前端面试题库 VS java后端面试题库大全 随着互联网技术的不断发展,前端开发已经成为了一项非常重要的职业。如果您希望成为一名高级前端开发工程师,那么以下几点建议可能会对您有所帮助。 1

    2024年02月12日
    浏览(55)
  • 浅谈ChatGPT取代前端开发工程师

    1.ChatGPT 是什么? ChatGPT 是一种基于深度学习的自然语言处理技术,它可以生成高质量的自然语言文本。该技术是由 OpenAI 团队 开发,旨在使计算机能够像人类一样理解和产生自然语言。ChatGPT 使用了深度神经网络和自然语言处理技术,通 过对大量语言数据的学习和建模,实现

    2023年04月08日
    浏览(42)
  • 50道web前端工程师面试题及答案解析,你学会了吗

    简介:本文包含了50个实用的前端面试题及答案解析,涵盖了HTML、CSS、JavaScript、DOM、Ajax、MVC、模块化、ES6、SPA、Webpack、Babel、Virtual DOM、响应式设计、移动优先设计、响应式图片、CSS 预处理器、后处理器、模块化、布局、盒模型、浮动、定位、动画、选择器、HTML5、WebGL、

    2024年02月02日
    浏览(86)
  • HTML5前端开发工程师的岗位职责说明(合集)

      HTML5前端开发工程师的岗位职责说明1 职责 1、根据产品设计文档和视觉文件,利用HTML5相关技术开发移动平台的web前端页面; 2、基于HTML5.0标准进行页面制作,编写可复用的用户界面组件; 3、持续的优化前端体验和页面响应速度,并保证兼容性和执行效率; 4、配合后台工程师

    2024年02月15日
    浏览(47)
  • 程序员必须掌握哪些算法?——前端开发工程师需要掌握的算法

    一个程序员一生中可能会邂逅各种各样的算法,但总有那么几种,是作为一个程序员一定会遇见且大概率需要掌握的算法。作为一名前端开发工程师,今天就通过这个话题和文章来聊聊前端开发工程师需要掌握的算法有哪些呢。 算法(Algorithm) 是指解题方案的准确而完整的

    2024年02月15日
    浏览(73)
  • mongodb安装教程(图解+链接,轻松拿到了阿里前端高级开发工程师的offer

    4.测试是否安装成功 进入C:Program FilesMongoDBServer3.4bin文件夹下,点击mongod.exe,如果闪一下退出,说明安装正常 5.安装服务 用管理员权限打开cmd命令行,输入如下命令安装mongodb服务 sc create MongoDB binPath= “C:Program FilesMongoDBServer3.4binmongod.exe --service --config=D:mongodbmongo.co

    2024年04月09日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包