WebAI(rust, wasm)

本文作者为 360 奇舞团前端开发工程师

引言

在现代 Web 开发中,将高性能计算和智能分析集成到前端应用中已经成为一种趋势。本文将介绍在超大型远洋航运颗/货船舶管理系统中,借助于 Rust 和 AI 技术实现 Web 侧 端智能数据分析处理海量传感器数据进行可视化界面的渲染工作,通过一系列的数据清洗、转换和分析,提升用户体验。

背景

数据分析与预处理的需求

在现代 Web 应用中,特别是在数据密集型的应用场景下,处理和展示海量数据是一项具有挑战性的任务。传统的前端技术堆栈,如 JavaScript 和框架库,虽然能提供基本的数据处理功能,但在面对复杂的数据分析任务时可能显得力不从心。这些任务包括数据清洗转换聚合、以及实时分析等。

随着数据规模的增大和分析需求的复杂化,前端应用需要能够高效地处理大量数据,同时保持良好的用户体验。例如,电商平台需要实时处理用户行为数据来提供个性化推荐,社交媒体应用需要分析用户生成内容以进行情感分析,金融应用则需要实时处理市场数据以进行预测和决策支持。

技术挑战

在浏览器环境中进行复杂的数据处理面临几个主要挑战:

  • 性能瓶颈:JavaScript 的单线程模型限制了其在处理大量数据时的性能,尤其是涉及到复杂计算和数据分析时。

  • 计算效率:在进行复杂数据处理时,JavaScript 的执行速度可能不够高效,特别是在处理海量数据时。

  • 资源消耗:前端的资源(如内存和计算能力)有限,处理大量数据可能导致浏览器变得响应迟钝或崩溃。

Rust 与 WebAssembly 的优势

为了应对这些挑战,Rust 和 WebAssembly (WASM) 的组合提供了一种解决方案。Rust 是一种系统编程语言,具有高效的内存管理和高性能的计算能力。WebAssembly 是一种可以在浏览器中运行的低级字节码格式,它允许开发者用非 JavaScript 语言编写代码,并将其编译为可以在浏览器中执行的高效二进制格式。

主要脉络

熟悉或者已经阅读过 Mdn 上面关于如何实现“Compiling from Rust to WebAssembly”的小伙伴可以跳过前面这一部分。

  • 介绍如何将 Rust Code 编译为 WASM,编译为 WebAssembly 并在 WEB 端集成。

  • 场景化演示:使用 Rust Code 实现海量数据的数据与处理与数据分析。

  • 探索与优化:能否集成端侧 AI 相关能力

环境准备

无需过多心理压力,本文中的代码会尽可能得将注释和用意阐述清晰完整,纵使对于 Rust 这门编程语言一窍不通的读者也不会有过多的阅读障碍。

首先我们需要安装 Rust 和 WebAssembly 工具链:
# 安装 Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 安装 wasm-pack
cargo install wasm-pack

# add WebAssembly target
rustup target add wasm32-unknown-unknown

此处对于不同操作系统的 PC 如何安装 Rust 可以参考https://www.rust-lang.org/tools/install,此处不做过多赘述。

wasm-pack,由于我们需要打包工具,所有这里引出了一个额外的工具--wasm-pack,有了 wasm-pack,我们可以将 rust code 编译为 WebAssembly,供给 WEB 端进行调用;

Rust 项目初始化

接下来我们在命令行输入类似于如下的命令:

cargo new --lib your-rust-project

此处为了方面演示,使用了 cargo new --lib rust-social-emotion-analysis 这个名称作为 Rust 项目根目录 在 cargo new --lib your-rust-project 命令执行过后,我们会看到本地多出了以下几个文件:

├── Cargo.toml
└── src
└── lib.rs

这里我们可以使用 Cargo.toml 对构建进行配置,Cargo.toml 类似于前端工程的 package.json 文件。

cd src,进入到 src 目录中之后,我们会发现 lib.rs 内部的几行代码:

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

这部分代码直接删除即可,然后替换为我们需要的 demo code:

// 引入 wasm_bindgen 库,这允许 Rust 代码与 WebAssembly 的 JavaScript 进行无缝交互
use wasm_bindgen::prelude::*;

// 使用 #[wasm_bindgen] 属性声明外部 JavaScript 函数
// 这里声明了一个名为 `alert` 的函数,该函数会在浏览器中弹出alert框
#[wasm_bindgen]
extern {
    // 外部函数 `alert`,用于显示alert对话框,接受一个字符串参数
    // 在浏览器环境中,这会弹出一个alert框显示传入的消息
    pub fn alert(s: &str);
}

// 使用 #[wasm_bindgen] 属性公开 Rust 函数,使其可被 JavaScript 代码调用
// 这个函数将用于向用户打招呼
#[wasm_bindgen]
pub fn greet(name: &str) {
    // 使用 Rust 的 `format!` 宏创建一个格式化的greet字符串
    // `format!` 宏将 `name` 参数插入到字符串中,生成类似 "Hello, John!" 的greet信息
    // 调用外部的 `alert` 函数来弹出显示该greet信息
    alert(&format!("Hello, {}!", name));
}

这里之所以引入 wasm_bindgen 这一 Crate 的原因是 wasm-pack 需要借助于 wasm-bindgen 来生成桥接 Javascript 与 Rust 的代码,进而使得我们可以在 JS 中调用 Rust API 或者 Rust 函数。

配置 Cargo.toml

打开项目的 cargo.toml 文件按照如下进行配置:

[package]
name = "hello-wasm"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
description = "A sample project with wasm-pack"
license = "MIT/Apache-2.0"
repository = "https://github.com/yourgithubusername/hello-wasm"
edition = "2018"

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

[dependencies]
wasm-bindgen = "0.2"

通过以上代码,首先我们添加了[package],其次通过[lib]这一部分是为了告知 Rust 我们想要打包成 cdylib 的版本。最后的[dependencies]是为了让 Cargo 知道我们依赖于 wasm-bindgen 0.2.x 版本。

构建

这里我们通过以下一段简短的命令进行构建:

wasm-pack build --target web
8f6bfde7346bda646420825bc37f482c.png

根据 Mozilla Hacks 上面的描述,简而言之以上代码大致做了以下五件事:

  • 将 Rust 编译为 WASM

  • 在新编译的 WASM 内部运行 wasm-bindgen,生成一个将新的 WebAssembly 文件以模块形式包裹引入到浏览器端的 js 文件

  • 新建一个 pkg 目录,并将上面最新生成的 js 和 webAssembly 文件移入其中

  • 读取项目的 Cargo.toml 文件并且据此生成 package.json

  • 将项目内部的 README.md 拷贝到 pkg 中

截至目前为止,我们的 Rust 项目目录基本如下:

├── Cargo.lock
├── Cargo.toml
├── index.html  <-- 新建一个 index.html 文件放置于Rust工程的根目录
├── pkg
│   ├── hello_wasm.d.ts
│   ├── hello_wasm.js
│   ├── hello_wasm_bg.wasm
│   ├── hello_wasm_bg.wasm.d.ts
│   └── package.json
├── src
│   └── lib.rs
└── target
    ├── CACHEDIR.TAG
    ├── release
    └── wasm32-unknown-unknown

在根目录新建一个 index.html 文件,并在其中编写如下代码:

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <title>hello-wasm example</title>
  </head>
  <body>
    <script type="module">
      import init, { greet } from "./pkg/hello_wasm.js";
      init().then(() => {
        greet("WebAssembly");
      });
    </script>
  </body>
</html>

OK,现在我们可以借助于 http-server 启动一个本地的静态资源服务,浏览页面效果:

npx http-server

至此,我们的第一步,即--“将 Rust Code 编译为 WASM,编译为 WebAssembly 并在 WEB 端集成”基本实现完成了。这部分为了方面演示省略了很多譬如压缩 Rust 转化为 WASM 文件的体积、以及老生常谈的 Rust 所有权和借用系统。

接下来我们看一下如何借助于 rust 实现海量数据的预处理与数据分析工作,以满足我们页面可视化渲染相关的需求。

大批量数据的预处理与分析

这一部分我们将重点讨论如何使用 Rust 的并发特性来提高数据处理的效率,并将结果集成到 WebAssembly 中以用于浏览器端的数据分析。

引入所需的库
// 引入 wasm-bindgen 库的预处理模块,用于与 JavaScript 交互
use wasm_bindgen::prelude::*;
定义数据结构
// 定义一个结构体来表示传感器数据项
// 结构体包含一个 id 和一个值,分别表示数据项的唯一标识符和传感器读取的值
#[derive(Clone, Debug)]
struct SensorData {
    id: u32,
    value: f64,
}
基础数据预处理
// 通过一个函数来对数据进行预处理,例如筛选出值大于某个阈值的数据
#[wasm_bindgen]
pub fn preprocess_sensor_data(data: &[SensorData], threshold: f64) -> Vec<SensorData> {
    // 使用迭代器和过滤方法筛选出符合条件的数据
    data.iter()
      .filter(|item| item.value > threshold)
      .cloned()
      .collect()
}
并发数据预处理

为了提高处理海量数据的效率,我们可以利用 Rust 的并发特性。使用 rayon 库,可以轻松实现数据的并行处理。

首先,添加 rayon 依赖到 Cargo.toml:

[dependencies]
wasm-bindgen = "0.2"
rayon = "1.5" // 添加 rayon 依赖

接下来,修改数据预处理函数以利用并发处理:

// 引入 rayon 库
use rayon::prelude::*;

// 并发数据预处理逻辑
#[wasm_bindgen]
pub fn parallel_preprocess_sensor_data(data: &[SensorData], threshold: f64) -> Vec<SensorData> {
    // 使用 rayon 的并行迭代器来加速数据预处理
    data.par_iter() // 使用并行迭代器
      .filter(|item| item.value > threshold)
      .cloned()
      .collect()
}

这里主要做了两件事:

  • parallel_preprocess_sensor_data 函数使用 rayon 的 par_iter() 方法将数据迭代并行化,从而提高处理速度。

  • rayon 会自动将任务分配到多个线程中,并且在合适的条件下使用并行计算来加速数据处理。

数据分析

基础数据分析:

// 对数据进行分析,例如计算数据的平均值
#[wasm_bindgen]
pub fn analyze_sensor_data(data: &[SensorData]) -> f64 {
    // 计算总和
    let sum: f64 = data.iter().map(|item| item.value).sum();
    // 计算平均值
    sum / data.len() as f64
}
并发数据分析

对于大数据集的并发分析,特别是需要进行复杂计算时,利用并发可以显著提高性能:

// 引入 rayon 库
use rayon::prelude::*;

// 并发分析计算数据的标准差
#[wasm_bindgen]
pub fn parallel_analyze_sensor_data(data: &[SensorData]) -> f64 {
    // 使用 rayon 的并行迭代器来加速数据分析
    let mean = analyze_sensor_data(data);

    let variance: f64 = data.par_iter() // 使用并行迭代器
      .map(|item| (item.value - mean).powi(2)) // 计算每个数据点与均值的平方差
      .sum(); // 汇总平方差

    // 计算标准差
    (variance / data.len() as f64).sqrt()
}

与前端工程的 Webpack 集成

在项目的 webpack.config.js 类似的配置文件中,添加 WebAssembly 支持:

const path = require("path");
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");

module.exports = {
  configureWebpack: {
    plugins: [
      new WasmPackPlugin({
        crateDirectory: path.resolve(__dirname, "rust_data_processing"),
      }),
    ],
    experiments: {
      asyncWebAssembly: true,
    },
  },
};

在 Vue 项目中的调用:

<template>
  <div>
    <h1>数据分析</h1>
    <p>预处理后的数据: {{ processedData }}</p>
    <p>分析结果: {{ analysisResult }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
import init, { parallel_preprocess_sensor_data, parallel_analyze_sensor_data } from 'rust_data_processing';

export default defineComponent({
  setup() {
    const processedData = ref<SensorData[]>([]);
    const analysisResult = ref<number | null>(null);

    onMounted(async () => {
      await init(); // 初始化 WebAssembly 模块

      const rawData: SensorData[] = [/* 海量传感器数据 */];
      const threshold = 10.0;

      // 预处理数据
      processedData.value = parallel_preprocess_sensor_data(rawData, threshold);

      // 分析数据
      analysisResult.value = parallel_analyze_sensor_data(processedData.value);
    });

    return { processedData, analysisResult };
  },
});
</script>

<style scoped>
/* 样式 */
</style>

端侧 AI 能力探索

这一部分我们主要探索如何编写数据处理代码

定义数据结构和实现数据预处理功能,例如数据标准化和特征提取:

// 引入必要的库
use wasm_bindgen::prelude::*;
use ndarray::Array2;
use ndarray_rand::RandomExt;
use ndarray_rand::rand_distr::Uniform;

// 定义数据结构
#[derive(Debug, Clone)]
pub struct SensorData {
    id: u32,
    value: f64,
}
// 实现数据预处理函数
#[wasm_bindgen]
pub fn preprocess_sensor_data(data: &[SensorData], threshold: f64) -> Vec<SensorData> {
    // 过滤掉值低于阈值的数据
    data.iter()
        .filter(|item| item.value > threshold)
        .cloned()
        .collect()
}

K-Means 聚类是一种广泛使用的无监督学习算法,用于将数据分组为 K 个簇。一个 Rust 实现的 K-Means 聚类库,适用于高性能计算。Rust 版 K-Means 实现主要有Rust K-Means —- Rust 实现的 K-Means 聚类库,适用于高性能计算以及rust-kmeans -- Crates.io 上的 rust-kmeans 库,可用于 K-Means 聚类算法的实现。

此处我们使用 ndarray 实现基本的聚类算法(K-Means)作为数据分析的一部分:

// 引入必要的库
use wasm_bindgen::prelude::*;
use ndarray::Array2;
use ndarray_rand::RandomExt;
use ndarray_rand::rand_distr::Uniform;

// 定义数据结构
#[derive(Debug, Clone)]
pub struct SensorData {
    id: u32,
    value: f64,
}

// 数据预处理
#[wasm_bindgen]
pub fn preprocess_sensor_data(data: &[SensorData], threshold: f64) -> Vec<SensorData> {
    // 过滤掉值低于阈值的数据
    data.iter()
        .filter(|item| item.value > threshold)
        .cloned()
        .collect()
}

结语

本文主要探索了如何利用 Rust 和 WebAssembly 的组合,将数据处理能力引入 Web 端。通过 Rust 的并发计算和 WASM 的高执行效率优势,尝试克服传统 JavaScript 的性能瓶颈,进而实现了在浏览器中进行复杂数据分析的可能性。随着 AI 快速发展的当下,Rust + WASM 凭借着高并发、高执行效率以及跨平台的优势,将会是端智能甚至是边缘计算中一个非常不错的选择。

参考文档

  • Compiling from Rust to WebAssembly: https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_Wasm

  • https://developer.mozilla.org/en-US/docs/WebAssembly

  • https://github.com/yewstack/yew

  • https://www.rust-lang.org/tools/installls

  • https://github.com/rust-ndarray/ndarray

  • https://en.wikipedia.org/wiki/K-means_clustering

  • https://docs.rs/ndarray/latest/ndarray/

- END -

如果您关注前端+AI 相关领域可以扫码进群交流

7a832a6a83a81ad650b91db80ec59246.png 7f456504531bb4dd850361d8ac990d77.jpeg

扫码进群2或添加小编微信进群1😊

关于奇舞团

奇舞团是 360 集团最大的大前端团队,非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

aecb9b984c475c6a3a2121907c4d0bbd.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值