应用于前端的自然断点法:wasm应用示例

某月某日,虾神本虾接到了这样一个需求,前端获取数据之后,在渲染之前,要对数据进行分类,分类的方法可以选择常用几种,例如等距法、自然断点法、标准差分类法等……

问:为什么要在前端?这种功能不是一般都是在后台实现的么?虽然JavaScript号称能够重写一切,但是这种写算法的事情,本身不是它的领域啊。

答:前端的数据来源可能是很多不同的系统,组合之后得到的,不一定来源于一个系统或者一个库表,如果让后台做,前后台之间的数据传递就太繁杂了,所以甲方想在前端把这事情给做了,再说: 

img

所以呢,虾神你不是做算法的么?来来来,帮忙给写一个……

虽然我手动写过自然断点法的计算方法,但是我不会JavaScript啊……你要不让我先花两个月,学习一下JavaScript?

img

虽然自然断点的算法还是比较简单的,我也写过:(见以前的文章:) 但是对于这种造轮子的活动,我一般是努力拒绝的:

img

所以我果断的把Python推荐给了他,毕竟Python里面已经有好几个身经百战的自然断点法的实现了,然后就毫无意外被拍回来了……

img

好吧,作为码农,不能说不行……我虽然不会写JavaScript,但是我会写Rust啊……好巧不巧,Rust写的wasm,就正好能在前端用。

首先简单介绍一下wasm

wasm: WebAssembly的简写,是一种新型的浏览器端代码:

img

用JavaScript的运行原理来说,它实际上是在JS的编译器中动态编译,然后在JS的VM中执行的,那么wasm可以让C/C++/Rust一类的高性能编译语言,转换成一种称之为IR的虚拟指令集,在需要的时候,在转换成JS VM可以运行的机器指令:

img

这种IR的编译指令,能够最大化的利用客户端的底层(如CPU\内存\显卡等)硬件,所以很多时候,比原生态的JavaScript性能更高。

img

2019年的时候,wasm就已经正式成为了W3C标准,成为了Web开发的“第四门语言

img

有关wasm的其他介绍,大家有兴趣的可以查阅其他资料,反正一句话:这玩意儿就是一个可以运行在前端浏览器上的编译级语言功能。

所以,我们就可以利用一些Rust写的东西,编译成JS可以用的脚本了。

秉承着有的轮子,我们就不用自己去造的原则,首先我们去看看Rust的官方仓库crates.io里面,有没有我们需要的东西:

很快,我告诉你,真的很快啊,就让我找到了这个东西:

img

然后就简单了——

我们直接去全球最大的同性交友网站gayhub……阿不,github,把这个包clone下来(或者你直接下载zip,然后解压也行),因为作者已经把所有的wasm相关代码都写好,所以你只需要运行编译打包就可以了,注意,官方文档上说-features如下:

wasm-pack build --release -- -features js

在我这里最新的Rust版本里面已经不好使了,直接编译为web就行,命令行如下:

wasm-pack build --release target web

然后看着cargo自动安装一堆东西,自动下载一堆东西,自动编译一堆东西,直到显示:

img

然后可以了,我们可以看见,在工程根目录下,会得到一个pkg包,里面有我们需要的wasm文件:

img

其中,index.html是调用的示例文件,搞前端的同学一眼就明白:

img

然后我们启动一个小http服务器,就可以看见效果了,我这里启动的是python自带的http服务器:

img

打开浏览器,F12看console:

img

我们可以简单解析一下这个工程(如果你Rust没有基础也没有兴趣,就可以跳过这一部分了),首先在src/jenks.rs文件中,写了一个函数,做了算法的实现:

//src/jenks.rs

pub fn get_jenks_breaks<T: ToPrimitive>(num_bins: usize, data: &[T]) -> Vec<f64> {
    //jenks的算法见文章开头的链接,这里不解释了
    //这里的就是用Rust把jenks的实现过程写了一遍。
}

这个函数在Rust工程里面是可以直接用的了,我们可以在下面写一个测试方法,来看看效果:

img

但是我们要在前端调用它,所以必须还要封装成wasm,所以还需要一个对外封装的接口: src/wasm.rs: 前面的#[wasm_bindgen]特性,就是声明该方法,是一个wasm的绑定,这样这个方法编译之后,就可以被前端调用了。

#[wasm_bindgen]
pub fn get_jenks_breaks(no_bins: usize, data: &[f64]) -> Box<[f64]> {
    let breaks = crate::jenks::get_jenks_breaks(no_bins, data);
    breaks.into_boxed_slice()
}

接下去,编译完成之后,会生成.wasm文件,这个文件是一个二进制的文件:

img

也就是我们前面说的IR(中间过程)文件。

然后前端需要调用的话,还得将它给调度到JS VM里面去,所以Rust的wasm工具包还会编译出方便前端调用的js/ts接口:

img

这样,你就得到了一个标准的Javascript接口,那么前端同学要调用,就没有任何的难度了……

至此,一个用后台高性能编译型语言编写的算法,就完成了前端封装和调用。

如果你有兴趣研究一下webassembly的话,这个例子我觉得可以当成hello world还好懂……起码你不用写一行代码,而具体里面的技术细节,有兴趣的同学可以查阅:

https://webassembly.org/

https://developer.mozilla.org/zh-CN/docs/WebAssembly

打完收工……