使用 rust 和 golang 体验 wasm

使用 rust 和 golang 体验 wasm

本来是来记录使用 rust 来做 wasm 的开发.但是整个过程相比 golang 体验 wasm 的开发实在是太流畅了,几乎是跟着文档走就结束了. 整体的感受是相比 golang , rust 的 wasm 的工具链非常完善.

跟着文档,几个命令就把整体的流程走通,没什么可记录的.

Rust

https://rustwasm.github.io/docs/book/

工具准备

  • yarn global add wasm-pack

https://rustwasm.github.io/wasm-pack/installer/

  • cargo install cargo-generate
  • npm install npm@latest -g

项目

创建项目: cargo generate --git https://github.com/rustwasm/wasm-pack-template

创建后设置项目名称,比如’rest_wasm_example’就完成了项目的创建,文件的 src 中包含了 lib.rs 导入了 alert函数, 导出了 greet 函数.

构建: wasm-pack build

Golang

相比 golang 需要使用 tinygo 来实现相关的开发,而且工具链没有 rust 完善. tinygo 的整个的文档就在下面的文档中,在官方代码中还有几个示例

https://tinygo.org/docs/guides/webassembly/wasm/

这个也足够我们搞清楚使用 tinygo 来完成 wasm 的开发.

感受

tinygo 相比 golang 生成出来的 wasm 文件要小得多. 但是和 rust 相比还是大了一些

在我运行生成的例子中,rust 生成的是 330 字节, tinygo 生成的 56KB.

rust 工具链完善,生成的 js 文件也更加工程化.用来和前端项目做集成会方便一些.

wasm 在浏览器里运行,有内存的限制.wasm 引擎预先分配一段内存,被称为线性内存. 在运行过程中,wasm 只能访问这段内存.着相当于一个虚拟的空间,在写程序时要时刻考虑内存占用的问题.

wasm 虽然是为浏览器出现的,但是它体现出的跨平台的特性,可能会成为一种基础底层的能力.

wasm 的出现,让浏览器作为载体,可能会出现真正的基于云的操作系统.

运行时场景思考

  • 从运行时的角度看

wasm 的运行时场景,有一些像 java 或 C# 的运行时, 比如在编译的时候会编译成为字节码,然后字节码在放到运行时里去运行.

wasm 的运行时就是 java 的 JRE 或者 c# 的 CLR . 可以通过任何的语言编译成为 wasm 后运行.java、c# 跨平台的特性变成的所有语言的特性. 可以想象 java 能做的事情, wasm 都能做.

  • 从容器的角度看

在看一些技术文章时,也有从容器的角度看看待 wasm , 假设运行时是一个安全的容器.那么 wasm 还欠缺什么呢.

程序运行时需要的资源有两种

  1. 计算资源:指的是 cpu 或者 gpu

    如何将计算资源标准化后供提供到 wasm 里面. 或者说 wasm 的运行时,如何提供底层计算资源的分配和限制.

  2. 存储资源:指的是内存

    wasm 提供了线性内存分配方式, 在 web 中是没有问题的, 但是在服务端作为密集运算的时候,可能会存在性能的问题.

  3. 输入输出端

    输入输出对于计算机语言已经有一个很好的虚拟,所有的语言都会提供一个 io 的基础库来负责输入输出. wasm 的运行时需要做一些输入输出的标准化的工作. 比如网卡(虚拟网络)、硬盘(虚拟文件)、键盘(虚拟输入输出)等等.

bookmark

在看各种运行时时,有些运行时已经做了上述的事情,比如 wasmedge 运行时为操作系统资源(例如文件系统、套接字、环境变量、进程)和内存空间提供隔离和保护.

从支持的语言看 wasmer 更多一些. wasmtime 作为字节码联盟,的到了一些大公司的支持, wasmedge 是 CNCF 的项目. 从资源的角度来看,可能 wasmtime 的发展会更好一些.

其他资源

https://wasmbyexample.dev/home.en-us.html 一些例子

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

https://wazero.io/ 使用 go 实现的 wasm 运行时

https://wasmtime.dev/ rust 实现的运行时,字节码联盟

https://wasmedge.org/ 运行时, CNCF 项目,

https://github.com/wasmerio/wasmer rust运行时

https://github.com/wapm-packages

用 Rust 求子串绝对值最大

用 Rust 求子串绝对值最大

这两天学习了 rust 的语法

bookmark

尝试使用 rust 来写今天的 leetcode 的每日一题

https://leetcode.cn/problems/maximum-absolute-sum-of-any-subarray

给你一个整数数组 nums 。一个子数组 [numsl, numsl+1, ..., numsr-1, numsr] 的 和的绝对值 为 abs(numsl + numsl+1 + ... + numsr-1 + numsr) 。

请你找出 nums 中 和的绝对值 最大的任意子数组(可能为空),并返回该 最大值 。

边查文档边写的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
impl Solution {

pub fn max_abs(a:i32,b:i32) -> i32 {

if a <0{
a= -a
}
if b<0{
b= -b
}

if a > b {
a
}else{
b
}
}

pub fn max_absolute_sum(nums: Vec<i32>) -> i32 {
let mut res = 0;

let mut dp_max = vec![0;nums.length()];
let mut dp_min = vec![0;nums.length()];

for (i,num) in nums.iter().enumerate() {
if i == 0 {
dp_max[0] = *num
dp_min[0] = *num
res = Solution::max_abs(dp_max[0],dp_min[0])
}else{
let max_num = dp_max[i-1] + *num
if max_num > *num{
dp_max[i] = max_num
}else{
dp_max[i] =*num
}

let min_num = dp_min[i-1] + *num
if min_num < *num{
dp_min[i] = min_num
}else{
dp_min[i] = *num
}

max_num = Solution::max_abs(dp_max[i],dp_min[i])
res = Solution::max_abs(max_num,res)
}
}
res
}
}

编译错误提示

  • 忘记写 ;
1
2
3
4
5
6
7
error: expected `;`, found `dp_min`
--> src/main.rs:60:33
|
60 | dp_max[0] = *num
| ^ help: add `;` here
61 | dp_min[0] = *num
| ------ unexpected token
  • 获取数组长度 len 写成 length
1
2
|         let mut dp_max = vec![0;nums.length()];
| ^^^^^^ help: there is a method with a similar name: `len`
  • 未定义的结构体
1
2
34 | impl Solution {
| ^^^^^^^^ not found in this scope
  • 最后的返回没有分号
1
2
3
4
5
6
7
pub fn max_absolute_sum(nums: Vec<i32>) -> i32 {
| ---------------- ^^^ expected `i32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
...
87 | res;
| - help: remove this semicolon to return this value
  • 计算最大绝对值函数的所有权
1
2
3
4
5
6
7
pub fn max_absolute_sum(nums: Vec<i32>) -> i32 {
| ---------------- ^^^ expected `i32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
...
87 | res;
| - help: remove this semicolon to return this value
  • 所有权只读
1
2
3
4
5
6
7
8
70 |                 let max_num = dp_max[i-1] + *num;
| -------
| |
| first assignment to `max_num`
| help: consider making this binding mutable: `mut max_num`
...
84 | max_num = Solution::max_abs(dp_max[i],dp_min[i]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign twice to immutable variable

修改的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
struct Solution{

}


impl Solution {

pub fn max_abs(a:i32,b:i32) -> i32 {
let mut a = a ;
let mut b = b ;
if a <0{
a= -a;
}
if b<0{
b= -b;
}

if a > b {
a
}else{
b
}
}

pub fn max_absolute_sum(nums: Vec<i32>) -> i32 {
let mut res = 0;

let mut dp_max = vec![0;nums.len()];
let mut dp_min = vec![0;nums.len()];

for (i,num) in nums.iter().enumerate() {
if i == 0 {
dp_max[0] = *num;
dp_min[0] = *num;
res = Solution::max_abs(dp_max[0],dp_min[0])
}else{
let mut max_num = dp_max[i-1] + *num;
if max_num > *num{
dp_max[i] = max_num;
}else{
dp_max[i] =*num;
}

let min_num = dp_min[i-1] + *num;
if min_num < *num{
dp_min[i] = min_num;
}else{
dp_min[i] = *num;
}

max_num = Solution::max_abs(dp_max[i],dp_min[i]);
res = Solution::max_abs(max_num,res);
}
}
res
}
}


fn main(){
Solution::max_absolute_sum(vec![1,-3,2,3,-4]);
}

总结

参看其他人写的代码

  • 直接使用 for i in 0..n 要比 nums.iter().enumerate() 要好一些
  • 数值型函数本身有 max min abs 函数,不需要自己写.
  • 计算时由于过程已经保存 dp 的结果也不用保存,只需要保存上一次的结果给下一次循环用即可.
  • rust 的语法确实很难描述,需要大量的实践,特别是开始写的时候变量的传递有些懵.

最终版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

//f(n) 绝对值最大 = n 的 最大值和最小值的绝对值取大
// f(n) 最大值 = f(n-1) 的最大值+n 和 n 取大
// f(n) 最小值 = f(n-1) 的最小值+n 和 n 取小

impl Solution {
pub fn max_absolute_sum(nums: Vec<i32>) -> i32 {
let n = nums.len();
let mut res = 0;

let mut dp_max = 0;
let mut dp_min = 0;

for i in 0..n {
if i == 0 {
dp_max = nums[0];
dp_min = nums[0];
res = dp_max.abs().max(dp_min.abs());
}else{

let max_num = dp_max + nums[i];
dp_max = max_num.max(nums[i]);

let min_num = dp_min + nums[i];
dp_min = min_num.min(nums[i]);

res = res.max(dp_max.abs().max(dp_min.abs()));

}
}
res
}
}
微服务还是单体服务?

微服务还是单体服务?

前两天看到了腾讯云开发者的账号发布以一篇 QQ 浏览器服务的优化.

bookmark

在做架构设计时将微服务变成单体服务. 我在下面做了评价.

很多做设计的没有意识到 rpc 也是需要时间的,无限度的搞微服务,复杂性和网络调用链导致问题排查不下去. 一个单体应用加个缓存的事情,最后搞成一堆服务相互依赖调用,浪费机器、浪费人力.

在做架构拆分,很容易忘记初衷是什么,而是为了架构而架构.

在做微服务的时候,基本都是目前架构冗余,业务杂糅在一起,扩容困难. 或者是数据库表太多,大量的表关联查询缓慢,所以要做微服务架构的改造,跟随一起的还有,服务要跟着一起上容器.

单体应用与微服务并不冲突

做微服务拆分,但不要拆得那么细,比如有些过分的拆分恨不得 tab 就成为一个微服务.

本来一个很简单的业务,一个微服务/单体应用就够了, 非得拆出好几个来,比如一个业务有几个任务,业务把每个任务都拆成一个服务,可每个服务的80%的代码都是一样的,甚至数据都是一个数据库来源. 没有这个必要嘛. 而且过多的拆分会导致业务代码的割裂.

grpc 很快,但是也需要时间

grpc 使用 http2 协议,相比 http 协议对比,长链接和流要快很多,但是还是需要花时间的,每多一次的 grpc 的交互,网络请求会多出 1-2ms 的时间.

有些给前端的页面渲染拆成了多个模块,当想要把所有数据一次渲染出来.内部可能需要做几次甚至是十几次的 rpc 调用. 导致在网络上的时间就超过了 20ms. 还是不考虑高负载和网络抖动的情况下.

当 rpc 出现错误重试的成本要远远高于单体应用出错重试.

让缓存前置,让请求后置

当对服务进行拆分,有时候会出现这样的情况,每个微服务配套一套缓存和 DB.通常我们在内网微服务通信,然后通过 http 来提供外网服务.如果可能的话,可以尝试让对外的服务做前置的缓存.

不要过早的优化,容器并不是银弹

容器也是有损耗的, 使用容器会带来物理机大概 5% 的性能损耗,如果业务本身很稳定,也不是高并发的系统,没有什么扩容的需求,要不还是用物理机吧.

容器带来的无状态服务,扩容等让运维变得简单,但是同时也带来了查问题的成本变高,全链路的监控和运维的成本,对于小团队来说几乎是个黑洞.

做架构升级不要为了兼容旧的东西做妥协

在做架构升级会碰到很多的技术债,为了兼容可能是错误的技术债,不得不为此妥协,当妥协的内容越来越多的时候,会发现架构升级做了个寂寞. (这个和微服务没关系)

架构的目的,不是为了炫耀新技术,而是为了稳定,作为一个技术人员,要最求新技术,善用老技术.